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.wal; 019 020import static org.apache.hadoop.hbase.wal.WALFactory.META_WAL_PROVIDER; 021import static org.apache.hadoop.hbase.wal.WALFactory.WAL_PROVIDER; 022import static org.junit.Assert.assertEquals; 023import static org.junit.Assert.assertFalse; 024import static org.junit.Assert.assertNotNull; 025import static org.junit.Assert.assertTrue; 026import static org.junit.Assert.fail; 027 028import java.io.IOException; 029import java.lang.reflect.Method; 030import java.net.BindException; 031import java.util.List; 032import java.util.NavigableMap; 033import java.util.TreeMap; 034import org.apache.hadoop.conf.Configuration; 035import org.apache.hadoop.fs.FSDataInputStream; 036import org.apache.hadoop.fs.FSDataOutputStream; 037import org.apache.hadoop.fs.FileStatus; 038import org.apache.hadoop.fs.FileSystem; 039import org.apache.hadoop.fs.Path; 040import org.apache.hadoop.hbase.Cell; 041import org.apache.hadoop.hbase.CellUtil; 042import org.apache.hadoop.hbase.Coprocessor; 043import org.apache.hadoop.hbase.HBaseClassTestRule; 044import org.apache.hadoop.hbase.HBaseTestingUtility; 045import org.apache.hadoop.hbase.HConstants; 046import org.apache.hadoop.hbase.KeyValue; 047import org.apache.hadoop.hbase.ServerName; 048import org.apache.hadoop.hbase.TableName; 049import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 050import org.apache.hadoop.hbase.client.RegionInfo; 051import org.apache.hadoop.hbase.client.RegionInfoBuilder; 052import org.apache.hadoop.hbase.client.TableDescriptor; 053import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 054import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 055import org.apache.hadoop.hbase.coprocessor.SampleRegionWALCoprocessor; 056import org.apache.hadoop.hbase.regionserver.MultiVersionConcurrencyControl; 057import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener; 058import org.apache.hadoop.hbase.regionserver.wal.WALCoprocessorHost; 059import org.apache.hadoop.hbase.testclassification.MediumTests; 060import org.apache.hadoop.hbase.testclassification.RegionServerTests; 061import org.apache.hadoop.hbase.util.Bytes; 062import org.apache.hadoop.hbase.util.CommonFSUtils; 063import org.apache.hadoop.hbase.util.FSUtils; 064import org.apache.hadoop.hbase.util.Threads; 065import org.apache.hadoop.hbase.wal.WALFactory.Providers; 066import org.apache.hadoop.hdfs.DistributedFileSystem; 067import org.apache.hadoop.hdfs.MiniDFSCluster; 068import org.apache.hadoop.hdfs.protocol.HdfsConstants; 069import org.junit.After; 070import org.junit.AfterClass; 071import org.junit.Before; 072import org.junit.BeforeClass; 073import org.junit.ClassRule; 074import org.junit.Rule; 075import org.junit.Test; 076import org.junit.experimental.categories.Category; 077import org.junit.rules.TestName; 078import org.slf4j.Logger; 079import org.slf4j.LoggerFactory; 080 081/** 082 * WAL tests that can be reused across providers. 083 */ 084@Category({RegionServerTests.class, MediumTests.class}) 085public class TestWALFactory { 086 087 @ClassRule 088 public static final HBaseClassTestRule CLASS_RULE = 089 HBaseClassTestRule.forClass(TestWALFactory.class); 090 091 private static final Logger LOG = LoggerFactory.getLogger(TestWALFactory.class); 092 093 protected static Configuration conf; 094 private static MiniDFSCluster cluster; 095 protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 096 protected static Path hbaseDir; 097 protected static Path hbaseWALDir; 098 099 protected FileSystem fs; 100 protected Path dir; 101 protected WALFactory wals; 102 private ServerName currentServername; 103 104 @Rule 105 public final TestName currentTest = new TestName(); 106 107 @Before 108 public void setUp() throws Exception { 109 fs = cluster.getFileSystem(); 110 dir = new Path(hbaseDir, currentTest.getMethodName()); 111 this.currentServername = ServerName.valueOf(currentTest.getMethodName(), 16010, 1); 112 wals = new WALFactory(conf, this.currentServername.toString()); 113 } 114 115 @After 116 public void tearDown() throws Exception { 117 // testAppendClose closes the FileSystem, which will prevent us from closing cleanly here. 118 try { 119 wals.close(); 120 } catch (IOException exception) { 121 LOG.warn("Encountered exception while closing wal factory. If you have other errors, this" + 122 " may be the cause. Message: " + exception); 123 LOG.debug("Exception details for failure to close wal factory.", exception); 124 } 125 FileStatus[] entries = fs.listStatus(new Path("/")); 126 for (FileStatus dir : entries) { 127 fs.delete(dir.getPath(), true); 128 } 129 } 130 131 @BeforeClass 132 public static void setUpBeforeClass() throws Exception { 133 CommonFSUtils.setWALRootDir(TEST_UTIL.getConfiguration(), new Path("file:///tmp/wal")); 134 // Make block sizes small. 135 TEST_UTIL.getConfiguration().setInt("dfs.blocksize", 1024 * 1024); 136 // needed for testAppendClose() 137 // quicker heartbeat interval for faster DN death notification 138 TEST_UTIL.getConfiguration().setInt("dfs.namenode.heartbeat.recheck-interval", 5000); 139 TEST_UTIL.getConfiguration().setInt("dfs.heartbeat.interval", 1); 140 TEST_UTIL.getConfiguration().setInt("dfs.client.socket-timeout", 5000); 141 142 // faster failover with cluster.shutdown();fs.close() idiom 143 TEST_UTIL.getConfiguration() 144 .setInt("hbase.ipc.client.connect.max.retries", 1); 145 TEST_UTIL.getConfiguration().setInt( 146 "dfs.client.block.recovery.retries", 1); 147 TEST_UTIL.getConfiguration().setInt( 148 "hbase.ipc.client.connection.maxidletime", 500); 149 TEST_UTIL.getConfiguration().setInt("hbase.lease.recovery.timeout", 10000); 150 TEST_UTIL.getConfiguration().setInt("hbase.lease.recovery.dfs.timeout", 1000); 151 TEST_UTIL.getConfiguration().set(CoprocessorHost.WAL_COPROCESSOR_CONF_KEY, 152 SampleRegionWALCoprocessor.class.getName()); 153 TEST_UTIL.startMiniDFSCluster(3); 154 155 conf = TEST_UTIL.getConfiguration(); 156 cluster = TEST_UTIL.getDFSCluster(); 157 158 hbaseDir = TEST_UTIL.createRootDir(); 159 hbaseWALDir = TEST_UTIL.createWALRootDir(); 160 } 161 162 @AfterClass 163 public static void tearDownAfterClass() throws Exception { 164 TEST_UTIL.shutdownMiniCluster(); 165 } 166 167 @Test 168 public void canCloseSingleton() throws IOException { 169 WALFactory.getInstance(conf).close(); 170 } 171 172 /** 173 * Just write multiple logs then split. Before fix for HADOOP-2283, this 174 * would fail. 175 * @throws IOException 176 */ 177 @Test 178 public void testSplit() throws IOException { 179 final TableName tableName = TableName.valueOf(currentTest.getMethodName()); 180 final byte [] rowName = tableName.getName(); 181 final MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl(1); 182 final int howmany = 3; 183 RegionInfo[] infos = new RegionInfo[3]; 184 Path tabledir = FSUtils.getWALTableDir(conf, tableName); 185 fs.mkdirs(tabledir); 186 for (int i = 0; i < howmany; i++) { 187 infos[i] = RegionInfoBuilder.newBuilder(tableName).setStartKey(Bytes.toBytes("" + i)) 188 .setEndKey(Bytes.toBytes("" + (i + 1))).build(); 189 fs.mkdirs(new Path(tabledir, infos[i].getEncodedName())); 190 LOG.info("allo " + new Path(tabledir, infos[i].getEncodedName()).toString()); 191 } 192 NavigableMap<byte[], Integer> scopes = new TreeMap<>(Bytes.BYTES_COMPARATOR); 193 scopes.put(Bytes.toBytes("column"), 0); 194 195 196 // Add edits for three regions. 197 for (int ii = 0; ii < howmany; ii++) { 198 for (int i = 0; i < howmany; i++) { 199 final WAL log = 200 wals.getWAL(infos[i]); 201 for (int j = 0; j < howmany; j++) { 202 WALEdit edit = new WALEdit(); 203 byte [] family = Bytes.toBytes("column"); 204 byte [] qualifier = Bytes.toBytes(Integer.toString(j)); 205 byte [] column = Bytes.toBytes("column:" + Integer.toString(j)); 206 edit.add(new KeyValue(rowName, family, qualifier, 207 System.currentTimeMillis(), column)); 208 LOG.info("Region " + i + ": " + edit); 209 WALKeyImpl walKey = new WALKeyImpl(infos[i].getEncodedNameAsBytes(), tableName, 210 System.currentTimeMillis(), mvcc, scopes); 211 log.append(infos[i], walKey, edit, true); 212 walKey.getWriteEntry(); 213 } 214 log.sync(); 215 log.rollWriter(true); 216 } 217 } 218 wals.shutdown(); 219 // The below calculation of logDir relies on insider information... WALSplitter should be connected better 220 // with the WAL system.... not requiring explicit path. The oldLogDir is just made up not used. 221 Path logDir = 222 new Path(new Path(hbaseWALDir, HConstants.HREGION_LOGDIR_NAME), 223 this.currentServername.toString()); 224 Path oldLogDir = new Path(hbaseDir, HConstants.HREGION_OLDLOGDIR_NAME); 225 List<Path> splits = WALSplitter.split(hbaseWALDir, logDir, oldLogDir, fs, conf, wals); 226 verifySplits(splits, howmany); 227 } 228 229 /** 230 * Test new HDFS-265 sync. 231 * @throws Exception 232 */ 233 @Test 234 public void Broken_testSync() throws Exception { 235 TableName tableName = TableName.valueOf(currentTest.getMethodName()); 236 MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl(1); 237 // First verify that using streams all works. 238 Path p = new Path(dir, currentTest.getMethodName() + ".fsdos"); 239 FSDataOutputStream out = fs.create(p); 240 out.write(tableName.getName()); 241 Method syncMethod = null; 242 try { 243 syncMethod = out.getClass().getMethod("hflush", new Class<?> []{}); 244 } catch (NoSuchMethodException e) { 245 try { 246 syncMethod = out.getClass().getMethod("sync", new Class<?> []{}); 247 } catch (NoSuchMethodException ex) { 248 fail("This version of Hadoop supports neither Syncable.sync() " + 249 "nor Syncable.hflush()."); 250 } 251 } 252 syncMethod.invoke(out, new Object[]{}); 253 FSDataInputStream in = fs.open(p); 254 assertTrue(in.available() > 0); 255 byte [] buffer = new byte [1024]; 256 int read = in.read(buffer); 257 assertEquals(tableName.getName().length, read); 258 out.close(); 259 in.close(); 260 261 final int total = 20; 262 WAL.Reader reader = null; 263 264 try { 265 RegionInfo info = RegionInfoBuilder.newBuilder(tableName).build(); 266 NavigableMap<byte[], Integer> scopes = new TreeMap<>(Bytes.BYTES_COMPARATOR); 267 scopes.put(tableName.getName(), 0); 268 final WAL wal = wals.getWAL(info); 269 270 for (int i = 0; i < total; i++) { 271 WALEdit kvs = new WALEdit(); 272 kvs.add(new KeyValue(Bytes.toBytes(i), tableName.getName(), tableName.getName())); 273 wal.append(info, new WALKeyImpl(info.getEncodedNameAsBytes(), tableName, 274 System.currentTimeMillis(), mvcc, scopes), kvs, true); 275 } 276 // Now call sync and try reading. Opening a Reader before you sync just 277 // gives you EOFE. 278 wal.sync(); 279 // Open a Reader. 280 Path walPath = AbstractFSWALProvider.getCurrentFileName(wal); 281 reader = wals.createReader(fs, walPath); 282 int count = 0; 283 WAL.Entry entry = new WAL.Entry(); 284 while ((entry = reader.next(entry)) != null) count++; 285 assertEquals(total, count); 286 reader.close(); 287 // Add test that checks to see that an open of a Reader works on a file 288 // that has had a sync done on it. 289 for (int i = 0; i < total; i++) { 290 WALEdit kvs = new WALEdit(); 291 kvs.add(new KeyValue(Bytes.toBytes(i), tableName.getName(), tableName.getName())); 292 wal.append(info, new WALKeyImpl(info.getEncodedNameAsBytes(), tableName, 293 System.currentTimeMillis(), mvcc, scopes), kvs, true); 294 } 295 wal.sync(); 296 reader = wals.createReader(fs, walPath); 297 count = 0; 298 while((entry = reader.next(entry)) != null) count++; 299 assertTrue(count >= total); 300 reader.close(); 301 // If I sync, should see double the edits. 302 wal.sync(); 303 reader = wals.createReader(fs, walPath); 304 count = 0; 305 while((entry = reader.next(entry)) != null) count++; 306 assertEquals(total * 2, count); 307 reader.close(); 308 // Now do a test that ensures stuff works when we go over block boundary, 309 // especially that we return good length on file. 310 final byte [] value = new byte[1025 * 1024]; // Make a 1M value. 311 for (int i = 0; i < total; i++) { 312 WALEdit kvs = new WALEdit(); 313 kvs.add(new KeyValue(Bytes.toBytes(i), tableName.getName(), value)); 314 wal.append(info, new WALKeyImpl(info.getEncodedNameAsBytes(), tableName, 315 System.currentTimeMillis(), mvcc, scopes), kvs, true); 316 } 317 // Now I should have written out lots of blocks. Sync then read. 318 wal.sync(); 319 reader = wals.createReader(fs, walPath); 320 count = 0; 321 while((entry = reader.next(entry)) != null) count++; 322 assertEquals(total * 3, count); 323 reader.close(); 324 // shutdown and ensure that Reader gets right length also. 325 wal.shutdown(); 326 reader = wals.createReader(fs, walPath); 327 count = 0; 328 while((entry = reader.next(entry)) != null) count++; 329 assertEquals(total * 3, count); 330 reader.close(); 331 } finally { 332 if (reader != null) reader.close(); 333 } 334 } 335 336 private void verifySplits(final List<Path> splits, final int howmany) 337 throws IOException { 338 assertEquals(howmany * howmany, splits.size()); 339 for (int i = 0; i < splits.size(); i++) { 340 LOG.info("Verifying=" + splits.get(i)); 341 WAL.Reader reader = wals.createReader(fs, splits.get(i)); 342 try { 343 int count = 0; 344 String previousRegion = null; 345 long seqno = -1; 346 WAL.Entry entry = new WAL.Entry(); 347 while((entry = reader.next(entry)) != null) { 348 WALKey key = entry.getKey(); 349 String region = Bytes.toString(key.getEncodedRegionName()); 350 // Assert that all edits are for same region. 351 if (previousRegion != null) { 352 assertEquals(previousRegion, region); 353 } 354 LOG.info("oldseqno=" + seqno + ", newseqno=" + key.getSequenceId()); 355 assertTrue(seqno < key.getSequenceId()); 356 seqno = key.getSequenceId(); 357 previousRegion = region; 358 count++; 359 } 360 assertEquals(howmany, count); 361 } finally { 362 reader.close(); 363 } 364 } 365 } 366 367 /* 368 * We pass different values to recoverFileLease() so that different code paths are covered 369 * 370 * For this test to pass, requires: 371 * 1. HDFS-200 (append support) 372 * 2. HDFS-988 (SafeMode should freeze file operations 373 * [FSNamesystem.nextGenerationStampForBlock]) 374 * 3. HDFS-142 (on restart, maintain pendingCreates) 375 */ 376 @Test 377 public void testAppendClose() throws Exception { 378 TableName tableName = 379 TableName.valueOf(currentTest.getMethodName()); 380 RegionInfo regionInfo = RegionInfoBuilder.newBuilder(tableName).build(); 381 382 WAL wal = wals.getWAL(regionInfo); 383 int total = 20; 384 385 NavigableMap<byte[], Integer> scopes = new TreeMap<>(Bytes.BYTES_COMPARATOR); 386 scopes.put(tableName.getName(), 0); 387 MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl(); 388 for (int i = 0; i < total; i++) { 389 WALEdit kvs = new WALEdit(); 390 kvs.add(new KeyValue(Bytes.toBytes(i), tableName.getName(), tableName.getName())); 391 wal.append(regionInfo, new WALKeyImpl(regionInfo.getEncodedNameAsBytes(), tableName, 392 System.currentTimeMillis(), mvcc, scopes), 393 kvs, true); 394 } 395 // Now call sync to send the data to HDFS datanodes 396 wal.sync(); 397 int namenodePort = cluster.getNameNodePort(); 398 final Path walPath = AbstractFSWALProvider.getCurrentFileName(wal); 399 400 401 // Stop the cluster. (ensure restart since we're sharing MiniDFSCluster) 402 try { 403 DistributedFileSystem dfs = cluster.getFileSystem(); 404 dfs.setSafeMode(HdfsConstants.SafeModeAction.SAFEMODE_ENTER); 405 TEST_UTIL.shutdownMiniDFSCluster(); 406 try { 407 // wal.writer.close() will throw an exception, 408 // but still call this since it closes the LogSyncer thread first 409 wal.shutdown(); 410 } catch (IOException e) { 411 LOG.info(e.toString(), e); 412 } 413 fs.close(); // closing FS last so DFSOutputStream can't call close 414 LOG.info("STOPPED first instance of the cluster"); 415 } finally { 416 // Restart the cluster 417 while (cluster.isClusterUp()){ 418 LOG.error("Waiting for cluster to go down"); 419 Thread.sleep(1000); 420 } 421 assertFalse(cluster.isClusterUp()); 422 cluster = null; 423 for (int i = 0; i < 100; i++) { 424 try { 425 cluster = TEST_UTIL.startMiniDFSClusterForTestWAL(namenodePort); 426 break; 427 } catch (BindException e) { 428 LOG.info("Sleeping. BindException bringing up new cluster"); 429 Threads.sleep(1000); 430 } 431 } 432 cluster.waitActive(); 433 fs = cluster.getFileSystem(); 434 LOG.info("STARTED second instance."); 435 } 436 437 // set the lease period to be 1 second so that the 438 // namenode triggers lease recovery upon append request 439 Method setLeasePeriod = cluster.getClass() 440 .getDeclaredMethod("setLeasePeriod", new Class[]{Long.TYPE, Long.TYPE}); 441 setLeasePeriod.setAccessible(true); 442 setLeasePeriod.invoke(cluster, 1000L, 1000L); 443 try { 444 Thread.sleep(1000); 445 } catch (InterruptedException e) { 446 LOG.info(e.toString(), e); 447 } 448 449 // Now try recovering the log, like the HMaster would do 450 final FileSystem recoveredFs = fs; 451 final Configuration rlConf = conf; 452 453 class RecoverLogThread extends Thread { 454 public Exception exception = null; 455 @Override 456 public void run() { 457 try { 458 FSUtils.getInstance(fs, rlConf) 459 .recoverFileLease(recoveredFs, walPath, rlConf, null); 460 } catch (IOException e) { 461 exception = e; 462 } 463 } 464 } 465 466 RecoverLogThread t = new RecoverLogThread(); 467 t.start(); 468 // Timeout after 60 sec. Without correct patches, would be an infinite loop 469 t.join(60 * 1000); 470 if(t.isAlive()) { 471 t.interrupt(); 472 throw new Exception("Timed out waiting for WAL.recoverLog()"); 473 } 474 475 if (t.exception != null) 476 throw t.exception; 477 478 // Make sure you can read all the content 479 WAL.Reader reader = wals.createReader(fs, walPath); 480 int count = 0; 481 WAL.Entry entry = new WAL.Entry(); 482 while (reader.next(entry) != null) { 483 count++; 484 assertTrue("Should be one KeyValue per WALEdit", 485 entry.getEdit().getCells().size() == 1); 486 } 487 assertEquals(total, count); 488 reader.close(); 489 490 // Reset the lease period 491 setLeasePeriod.invoke(cluster, new Object[]{ 60000L, 3600000L }); 492 } 493 494 /** 495 * Tests that we can write out an edit, close, and then read it back in again. 496 */ 497 @Test 498 public void testEditAdd() throws IOException { 499 int colCount = 10; 500 TableDescriptor htd = 501 TableDescriptorBuilder.newBuilder(TableName.valueOf(currentTest.getMethodName())) 502 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("column")).build(); 503 NavigableMap<byte[], Integer> scopes = new TreeMap<byte[], Integer>(Bytes.BYTES_COMPARATOR); 504 for (byte[] fam : htd.getColumnFamilyNames()) { 505 scopes.put(fam, 0); 506 } 507 byte[] row = Bytes.toBytes("row"); 508 WAL.Reader reader = null; 509 try { 510 final MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl(1); 511 512 // Write columns named 1, 2, 3, etc. and then values of single byte 513 // 1, 2, 3... 514 long timestamp = System.currentTimeMillis(); 515 WALEdit cols = new WALEdit(); 516 for (int i = 0; i < colCount; i++) { 517 cols.add(new KeyValue(row, Bytes.toBytes("column"), 518 Bytes.toBytes(Integer.toString(i)), 519 timestamp, new byte[] { (byte)(i + '0') })); 520 } 521 RegionInfo info = RegionInfoBuilder.newBuilder(htd.getTableName()).setStartKey(row) 522 .setEndKey(Bytes.toBytes(Bytes.toString(row) + "1")).build(); 523 final WAL log = wals.getWAL(info); 524 525 final long txid = log.append(info, 526 new WALKeyImpl(info.getEncodedNameAsBytes(), htd.getTableName(), System.currentTimeMillis(), 527 mvcc, scopes), 528 cols, true); 529 log.sync(txid); 530 log.startCacheFlush(info.getEncodedNameAsBytes(), htd.getColumnFamilyNames()); 531 log.completeCacheFlush(info.getEncodedNameAsBytes()); 532 log.shutdown(); 533 Path filename = AbstractFSWALProvider.getCurrentFileName(log); 534 // Now open a reader on the log and assert append worked. 535 reader = wals.createReader(fs, filename); 536 // Above we added all columns on a single row so we only read one 537 // entry in the below... thats why we have '1'. 538 for (int i = 0; i < 1; i++) { 539 WAL.Entry entry = reader.next(null); 540 if (entry == null) break; 541 WALKey key = entry.getKey(); 542 WALEdit val = entry.getEdit(); 543 assertTrue(Bytes.equals(info.getEncodedNameAsBytes(), key.getEncodedRegionName())); 544 assertTrue(htd.getTableName().equals(key.getTableName())); 545 Cell cell = val.getCells().get(0); 546 assertTrue(Bytes.equals(row, 0, row.length, cell.getRowArray(), cell.getRowOffset(), 547 cell.getRowLength())); 548 assertEquals((byte)(i + '0'), CellUtil.cloneValue(cell)[0]); 549 System.out.println(key + " " + val); 550 } 551 } finally { 552 if (reader != null) { 553 reader.close(); 554 } 555 } 556 } 557 558 @Test 559 public void testAppend() throws IOException { 560 int colCount = 10; 561 TableDescriptor htd = 562 TableDescriptorBuilder.newBuilder(TableName.valueOf(currentTest.getMethodName())) 563 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("column")).build(); 564 NavigableMap<byte[], Integer> scopes = new TreeMap<byte[], Integer>(Bytes.BYTES_COMPARATOR); 565 for (byte[] fam : htd.getColumnFamilyNames()) { 566 scopes.put(fam, 0); 567 } 568 byte[] row = Bytes.toBytes("row"); 569 WAL.Reader reader = null; 570 final MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl(1); 571 try { 572 // Write columns named 1, 2, 3, etc. and then values of single byte 573 // 1, 2, 3... 574 long timestamp = System.currentTimeMillis(); 575 WALEdit cols = new WALEdit(); 576 for (int i = 0; i < colCount; i++) { 577 cols.add(new KeyValue(row, Bytes.toBytes("column"), 578 Bytes.toBytes(Integer.toString(i)), 579 timestamp, new byte[] { (byte)(i + '0') })); 580 } 581 RegionInfo hri = RegionInfoBuilder.newBuilder(htd.getTableName()).build(); 582 final WAL log = wals.getWAL(hri); 583 final long txid = log.append(hri, 584 new WALKeyImpl(hri.getEncodedNameAsBytes(), htd.getTableName(), System.currentTimeMillis(), 585 mvcc, scopes), 586 cols, true); 587 log.sync(txid); 588 log.startCacheFlush(hri.getEncodedNameAsBytes(), htd.getColumnFamilyNames()); 589 log.completeCacheFlush(hri.getEncodedNameAsBytes()); 590 log.shutdown(); 591 Path filename = AbstractFSWALProvider.getCurrentFileName(log); 592 // Now open a reader on the log and assert append worked. 593 reader = wals.createReader(fs, filename); 594 WAL.Entry entry = reader.next(); 595 assertEquals(colCount, entry.getEdit().size()); 596 int idx = 0; 597 for (Cell val : entry.getEdit().getCells()) { 598 assertTrue(Bytes.equals(hri.getEncodedNameAsBytes(), 599 entry.getKey().getEncodedRegionName())); 600 assertTrue(htd.getTableName().equals(entry.getKey().getTableName())); 601 assertTrue(Bytes.equals(row, 0, row.length, val.getRowArray(), val.getRowOffset(), 602 val.getRowLength())); 603 assertEquals((byte) (idx + '0'), CellUtil.cloneValue(val)[0]); 604 System.out.println(entry.getKey() + " " + val); 605 idx++; 606 } 607 } finally { 608 if (reader != null) { 609 reader.close(); 610 } 611 } 612 } 613 614 /** 615 * Test that we can visit entries before they are appended 616 * @throws Exception 617 */ 618 @Test 619 public void testVisitors() throws Exception { 620 final int COL_COUNT = 10; 621 final TableName tableName = TableName.valueOf(currentTest.getMethodName()); 622 final byte [] row = Bytes.toBytes("row"); 623 final DumbWALActionsListener visitor = new DumbWALActionsListener(); 624 final MultiVersionConcurrencyControl mvcc = new MultiVersionConcurrencyControl(1); 625 long timestamp = System.currentTimeMillis(); 626 NavigableMap<byte[], Integer> scopes = new TreeMap<>(Bytes.BYTES_COMPARATOR); 627 scopes.put(Bytes.toBytes("column"), 0); 628 629 RegionInfo hri = RegionInfoBuilder.newBuilder(tableName).build(); 630 final WAL log = wals.getWAL(hri); 631 log.registerWALActionsListener(visitor); 632 for (int i = 0; i < COL_COUNT; i++) { 633 WALEdit cols = new WALEdit(); 634 cols.add(new KeyValue(row, Bytes.toBytes("column"), 635 Bytes.toBytes(Integer.toString(i)), 636 timestamp, new byte[]{(byte) (i + '0')})); 637 log.append(hri, new WALKeyImpl(hri.getEncodedNameAsBytes(), tableName, 638 System.currentTimeMillis(), mvcc, scopes), cols, true); 639 } 640 log.sync(); 641 assertEquals(COL_COUNT, visitor.increments); 642 log.unregisterWALActionsListener(visitor); 643 WALEdit cols = new WALEdit(); 644 cols.add(new KeyValue(row, Bytes.toBytes("column"), 645 Bytes.toBytes(Integer.toString(11)), 646 timestamp, new byte[]{(byte) (11 + '0')})); 647 log.append(hri, new WALKeyImpl(hri.getEncodedNameAsBytes(), tableName, 648 System.currentTimeMillis(), mvcc, scopes), cols, true); 649 log.sync(); 650 assertEquals(COL_COUNT, visitor.increments); 651 } 652 653 /** 654 * A loaded WAL coprocessor won't break existing WAL test cases. 655 */ 656 @Test 657 public void testWALCoprocessorLoaded() throws Exception { 658 // test to see whether the coprocessor is loaded or not. 659 WALCoprocessorHost host = wals.getWAL(null).getCoprocessorHost(); 660 Coprocessor c = host.findCoprocessor(SampleRegionWALCoprocessor.class); 661 assertNotNull(c); 662 } 663 664 static class DumbWALActionsListener implements WALActionsListener { 665 int increments = 0; 666 667 @Override 668 public void visitLogEntryBeforeWrite(RegionInfo info, WALKey logKey, WALEdit logEdit) { 669 increments++; 670 } 671 672 @Override 673 public void visitLogEntryBeforeWrite(WALKey logKey, WALEdit logEdit) { 674 // To change body of implemented methods use File | Settings | File 675 // Templates. 676 increments++; 677 } 678 } 679 680 @Test 681 public void testWALProviders() throws IOException { 682 Configuration conf = new Configuration(); 683 WALFactory walFactory = new WALFactory(conf, this.currentServername.toString()); 684 assertEquals(walFactory.getWALProvider().getClass(), walFactory.getMetaProvider().getClass()); 685 } 686 687 @Test 688 public void testOnlySetWALProvider() throws IOException { 689 Configuration conf = new Configuration(); 690 conf.set(WAL_PROVIDER, WALFactory.Providers.multiwal.name()); 691 WALFactory walFactory = new WALFactory(conf, this.currentServername.toString()); 692 693 assertEquals(WALFactory.Providers.multiwal.clazz, walFactory.getWALProvider().getClass()); 694 assertEquals(WALFactory.Providers.multiwal.clazz, walFactory.getMetaProvider().getClass()); 695 } 696 697 @Test 698 public void testOnlySetMetaWALProvider() throws IOException { 699 Configuration conf = new Configuration(); 700 conf.set(META_WAL_PROVIDER, WALFactory.Providers.asyncfs.name()); 701 WALFactory walFactory = new WALFactory(conf, this.currentServername.toString()); 702 703 assertEquals(WALFactory.Providers.defaultProvider.clazz, 704 walFactory.getWALProvider().getClass()); 705 assertEquals(WALFactory.Providers.asyncfs.clazz, walFactory.getMetaProvider().getClass()); 706 } 707 708 @Test 709 public void testDefaultProvider() throws IOException { 710 final Configuration conf = new Configuration(); 711 // AsyncFSWal is the default, we should be able to request any WAL. 712 final WALFactory normalWalFactory = new WALFactory(conf, this.currentServername.toString()); 713 Class<? extends WALProvider> fshLogProvider = normalWalFactory.getProviderClass( 714 WALFactory.WAL_PROVIDER, Providers.filesystem.name()); 715 assertEquals(Providers.filesystem.clazz, fshLogProvider); 716 717 // Imagine a world where MultiWAL is the default 718 final WALFactory customizedWalFactory = new WALFactory( 719 conf, this.currentServername.toString()) { 720 @Override 721 Providers getDefaultProvider() { 722 return Providers.multiwal; 723 } 724 }; 725 // If we don't specify a WALProvider, we should get the default implementation. 726 Class<? extends WALProvider> multiwalProviderClass = customizedWalFactory.getProviderClass( 727 WALFactory.WAL_PROVIDER, Providers.multiwal.name()); 728 assertEquals(Providers.multiwal.clazz, multiwalProviderClass); 729 } 730 731 @Test 732 public void testCustomProvider() throws IOException { 733 final Configuration config = new Configuration(); 734 config.set(WALFactory.WAL_PROVIDER, IOTestProvider.class.getName()); 735 final WALFactory walFactory = new WALFactory(config, this.currentServername.toString()); 736 Class<? extends WALProvider> walProvider = walFactory.getProviderClass( 737 WALFactory.WAL_PROVIDER, Providers.filesystem.name()); 738 assertEquals(IOTestProvider.class, walProvider); 739 WALProvider metaWALProvider = walFactory.getMetaProvider(); 740 assertEquals(IOTestProvider.class, metaWALProvider.getClass()); 741 } 742 743 @Test 744 public void testCustomMetaProvider() throws IOException { 745 final Configuration config = new Configuration(); 746 config.set(WALFactory.META_WAL_PROVIDER, IOTestProvider.class.getName()); 747 final WALFactory walFactory = new WALFactory(config, this.currentServername.toString()); 748 Class<? extends WALProvider> walProvider = walFactory.getProviderClass( 749 WALFactory.WAL_PROVIDER, Providers.filesystem.name()); 750 assertEquals(Providers.filesystem.clazz, walProvider); 751 WALProvider metaWALProvider = walFactory.getMetaProvider(); 752 assertEquals(IOTestProvider.class, metaWALProvider.getClass()); 753 } 754}