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.procedure2.store.wal; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023 024import java.io.FileNotFoundException; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.OutputStream; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Comparator; 031import java.util.HashSet; 032import java.util.Set; 033import java.util.concurrent.atomic.AtomicLong; 034import org.apache.hadoop.conf.Configuration; 035import org.apache.hadoop.fs.FileStatus; 036import org.apache.hadoop.fs.FileSystem; 037import org.apache.hadoop.fs.Path; 038import org.apache.hadoop.hbase.HBaseClassTestRule; 039import org.apache.hadoop.hbase.HBaseCommonTestingUtil; 040import org.apache.hadoop.hbase.HConstants; 041import org.apache.hadoop.hbase.procedure2.Procedure; 042import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 043import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; 044import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility.LoadCounter; 045import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility.TestProcedure; 046import org.apache.hadoop.hbase.procedure2.SequentialProcedure; 047import org.apache.hadoop.hbase.procedure2.store.LeaseRecovery; 048import org.apache.hadoop.hbase.procedure2.store.ProcedureStore; 049import org.apache.hadoop.hbase.testclassification.MasterTests; 050import org.apache.hadoop.hbase.testclassification.SmallTests; 051import org.apache.hadoop.io.IOUtils; 052import org.junit.After; 053import org.junit.Before; 054import org.junit.ClassRule; 055import org.junit.Test; 056import org.junit.experimental.categories.Category; 057import org.mockito.Mockito; 058import org.mockito.invocation.InvocationOnMock; 059import org.mockito.stubbing.Answer; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063import org.apache.hbase.thirdparty.com.google.protobuf.Int64Value; 064 065@Category({ MasterTests.class, SmallTests.class }) 066public class TestWALProcedureStore { 067 @ClassRule 068 public static final HBaseClassTestRule CLASS_RULE = 069 HBaseClassTestRule.forClass(TestWALProcedureStore.class); 070 071 private static final Logger LOG = LoggerFactory.getLogger(TestWALProcedureStore.class); 072 073 private static final int PROCEDURE_STORE_SLOTS = 1; 074 075 private WALProcedureStore procStore; 076 077 private final HBaseCommonTestingUtil htu = new HBaseCommonTestingUtil(); 078 private FileSystem fs; 079 private Path testDir; 080 private Path logDir; 081 082 private void setupConfig(final Configuration conf) { 083 conf.setBoolean(WALProcedureStore.EXEC_WAL_CLEANUP_ON_LOAD_CONF_KEY, true); 084 } 085 086 @Before 087 public void setUp() throws IOException { 088 testDir = htu.getDataTestDir(); 089 htu.getConfiguration().set(HConstants.HBASE_DIR, testDir.toString()); 090 fs = testDir.getFileSystem(htu.getConfiguration()); 091 htu.getConfiguration().set(HConstants.HBASE_DIR, testDir.toString()); 092 assertTrue(testDir.depth() > 1); 093 094 TestSequentialProcedure.seqId.set(0); 095 setupConfig(htu.getConfiguration()); 096 logDir = new Path(testDir, "proc-logs"); 097 procStore = ProcedureTestingUtility.createWalStore(htu.getConfiguration(), logDir); 098 procStore.start(PROCEDURE_STORE_SLOTS); 099 procStore.recoverLease(); 100 procStore.load(new LoadCounter()); 101 } 102 103 @After 104 public void tearDown() throws IOException { 105 procStore.stop(false); 106 fs.delete(logDir, true); 107 } 108 109 private void storeRestart(ProcedureStore.ProcedureLoader loader) throws Exception { 110 ProcedureTestingUtility.storeRestart(procStore, loader); 111 } 112 113 @Test 114 public void testEmptyRoll() throws Exception { 115 for (int i = 0; i < 10; ++i) { 116 procStore.periodicRollForTesting(); 117 } 118 assertEquals(1, procStore.getActiveLogs().size()); 119 FileStatus[] status = fs.listStatus(logDir); 120 assertEquals(1, status.length); 121 } 122 123 @Test 124 public void testRestartWithoutData() throws Exception { 125 for (int i = 0; i < 10; ++i) { 126 final LoadCounter loader = new LoadCounter(); 127 storeRestart(loader); 128 } 129 LOG.info("ACTIVE WALs " + procStore.getActiveLogs()); 130 assertEquals(1, procStore.getActiveLogs().size()); 131 FileStatus[] status = fs.listStatus(logDir); 132 assertEquals(1, status.length); 133 } 134 135 /** 136 * Tests that tracker for all old logs are loaded back after procedure store is restarted. 137 */ 138 @Test 139 public void trackersLoadedForAllOldLogs() throws Exception { 140 for (int i = 0; i <= 20; ++i) { 141 procStore.insert(new TestProcedure(i), null); 142 if (i > 0 && (i % 5) == 0) { 143 LoadCounter loader = new LoadCounter(); 144 storeRestart(loader); 145 } 146 } 147 assertEquals(5, procStore.getActiveLogs().size()); 148 for (int i = 0; i < procStore.getActiveLogs().size() - 1; ++i) { 149 ProcedureStoreTracker tracker = procStore.getActiveLogs().get(i).getTracker(); 150 assertTrue(tracker != null && !tracker.isEmpty()); 151 } 152 } 153 154 @Test 155 public void testWalCleanerSequentialClean() throws Exception { 156 final Procedure<?>[] procs = new Procedure[5]; 157 ArrayList<ProcedureWALFile> logs = null; 158 159 // Insert procedures and roll wal after every insert. 160 for (int i = 0; i < procs.length; i++) { 161 procs[i] = new TestSequentialProcedure(); 162 procStore.insert(procs[i], null); 163 procStore.rollWriterForTesting(); 164 logs = procStore.getActiveLogs(); 165 assertEquals(logs.size(), i + 2); // Extra 1 for current ongoing wal. 166 } 167 168 // Delete procedures in sequential order make sure that only the corresponding wal is deleted 169 // from logs list. 170 final int[] deleteOrder = new int[] { 0, 1, 2, 3, 4 }; 171 for (int i = 0; i < deleteOrder.length; i++) { 172 procStore.delete(procs[deleteOrder[i]].getProcId()); 173 procStore.removeInactiveLogsForTesting(); 174 assertFalse(logs.get(deleteOrder[i]).toString(), 175 procStore.getActiveLogs().contains(logs.get(deleteOrder[i]))); 176 assertEquals(procStore.getActiveLogs().size(), procs.length - i); 177 } 178 } 179 180 // Test that wal cleaner doesn't create holes in wal files list i.e. it only deletes files if 181 // they are in the starting of the list. 182 @Test 183 public void testWalCleanerNoHoles() throws Exception { 184 final Procedure<?>[] procs = new Procedure[5]; 185 ArrayList<ProcedureWALFile> logs = null; 186 // Insert procedures and roll wal after every insert. 187 for (int i = 0; i < procs.length; i++) { 188 procs[i] = new TestSequentialProcedure(); 189 procStore.insert(procs[i], null); 190 procStore.rollWriterForTesting(); 191 logs = procStore.getActiveLogs(); 192 assertEquals(i + 2, logs.size()); // Extra 1 for current ongoing wal. 193 } 194 195 for (int i = 1; i < procs.length; i++) { 196 procStore.delete(procs[i].getProcId()); 197 } 198 assertEquals(procs.length + 1, procStore.getActiveLogs().size()); 199 procStore.delete(procs[0].getProcId()); 200 assertEquals(1, procStore.getActiveLogs().size()); 201 } 202 203 @Test 204 public void testWalCleanerUpdates() throws Exception { 205 TestSequentialProcedure p1 = new TestSequentialProcedure(); 206 TestSequentialProcedure p2 = new TestSequentialProcedure(); 207 procStore.insert(p1, null); 208 procStore.insert(p2, null); 209 procStore.rollWriterForTesting(); 210 ProcedureWALFile firstLog = procStore.getActiveLogs().get(0); 211 procStore.update(p1); 212 procStore.rollWriterForTesting(); 213 procStore.update(p2); 214 procStore.rollWriterForTesting(); 215 procStore.removeInactiveLogsForTesting(); 216 assertFalse(procStore.getActiveLogs().contains(firstLog)); 217 } 218 219 @Test 220 public void testWalCleanerUpdatesDontLeaveHoles() throws Exception { 221 TestSequentialProcedure p1 = new TestSequentialProcedure(); 222 TestSequentialProcedure p2 = new TestSequentialProcedure(); 223 procStore.insert(p1, null); 224 procStore.insert(p2, null); 225 procStore.rollWriterForTesting(); // generates first log with p1 + p2 226 ProcedureWALFile log1 = procStore.getActiveLogs().get(0); 227 procStore.update(p2); 228 procStore.rollWriterForTesting(); // generates second log with p2 229 ProcedureWALFile log2 = procStore.getActiveLogs().get(1); 230 procStore.update(p2); 231 procStore.rollWriterForTesting(); // generates third log with p2 232 procStore.removeInactiveLogsForTesting(); // Shouldn't remove 2nd log. 233 assertEquals(4, procStore.getActiveLogs().size()); 234 procStore.update(p1); 235 procStore.rollWriterForTesting(); // generates fourth log with p1 236 procStore.removeInactiveLogsForTesting(); // Should remove first two logs. 237 assertEquals(3, procStore.getActiveLogs().size()); 238 assertFalse(procStore.getActiveLogs().contains(log1)); 239 assertFalse(procStore.getActiveLogs().contains(log2)); 240 } 241 242 @Test 243 public void testWalCleanerWithEmptyRolls() throws Exception { 244 final Procedure<?>[] procs = new Procedure[3]; 245 for (int i = 0; i < procs.length; ++i) { 246 procs[i] = new TestSequentialProcedure(); 247 procStore.insert(procs[i], null); 248 } 249 assertEquals(1, procStore.getActiveLogs().size()); 250 procStore.rollWriterForTesting(); 251 assertEquals(2, procStore.getActiveLogs().size()); 252 procStore.rollWriterForTesting(); 253 assertEquals(3, procStore.getActiveLogs().size()); 254 255 for (int i = 0; i < procs.length; ++i) { 256 procStore.update(procs[i]); 257 procStore.rollWriterForTesting(); 258 procStore.rollWriterForTesting(); 259 if (i < (procs.length - 1)) { 260 assertEquals(3 + ((i + 1) * 2), procStore.getActiveLogs().size()); 261 } 262 } 263 assertEquals(7, procStore.getActiveLogs().size()); 264 265 for (int i = 0; i < procs.length; ++i) { 266 procStore.delete(procs[i].getProcId()); 267 assertEquals(7 - ((i + 1) * 2), procStore.getActiveLogs().size()); 268 } 269 assertEquals(1, procStore.getActiveLogs().size()); 270 } 271 272 @Test 273 public void testEmptyLogLoad() throws Exception { 274 LoadCounter loader = new LoadCounter(); 275 storeRestart(loader); 276 assertEquals(0, loader.getMaxProcId()); 277 assertEquals(0, loader.getLoadedCount()); 278 assertEquals(0, loader.getCorruptedCount()); 279 } 280 281 @Test 282 public void testLoad() throws Exception { 283 Set<Long> procIds = new HashSet<>(); 284 285 // Insert something in the log 286 Procedure<?> proc1 = new TestSequentialProcedure(); 287 procIds.add(proc1.getProcId()); 288 procStore.insert(proc1, null); 289 290 Procedure<?> proc2 = new TestSequentialProcedure(); 291 Procedure<?>[] child2 = new Procedure[2]; 292 child2[0] = new TestSequentialProcedure(); 293 child2[1] = new TestSequentialProcedure(); 294 295 procIds.add(proc2.getProcId()); 296 procIds.add(child2[0].getProcId()); 297 procIds.add(child2[1].getProcId()); 298 procStore.insert(proc2, child2); 299 300 // Verify that everything is there 301 verifyProcIdsOnRestart(procIds); 302 303 // Update and delete something 304 procStore.update(proc1); 305 procStore.update(child2[1]); 306 procStore.delete(child2[1].getProcId()); 307 procIds.remove(child2[1].getProcId()); 308 309 // Verify that everything is there 310 verifyProcIdsOnRestart(procIds); 311 312 // Remove 4 byte from the trailers 313 procStore.stop(false); 314 FileStatus[] logs = fs.listStatus(logDir); 315 assertEquals(3, logs.length); 316 for (int i = 0; i < logs.length; ++i) { 317 corruptLog(logs[i], 4); 318 } 319 verifyProcIdsOnRestart(procIds); 320 } 321 322 @Test 323 public void testNoTrailerDoubleRestart() throws Exception { 324 // log-0001: proc 0, 1 and 2 are inserted 325 Procedure<?> proc0 = new TestSequentialProcedure(); 326 procStore.insert(proc0, null); 327 Procedure<?> proc1 = new TestSequentialProcedure(); 328 procStore.insert(proc1, null); 329 Procedure<?> proc2 = new TestSequentialProcedure(); 330 procStore.insert(proc2, null); 331 procStore.rollWriterForTesting(); 332 333 // log-0002: proc 1 deleted 334 procStore.delete(proc1.getProcId()); 335 procStore.rollWriterForTesting(); 336 337 // log-0003: proc 2 is update 338 procStore.update(proc2); 339 procStore.rollWriterForTesting(); 340 341 // log-0004: proc 2 deleted 342 procStore.delete(proc2.getProcId()); 343 344 // stop the store and remove the trailer 345 procStore.stop(false); 346 FileStatus[] logs = fs.listStatus(logDir); 347 assertEquals(4, logs.length); 348 for (int i = 0; i < logs.length; ++i) { 349 corruptLog(logs[i], 4); 350 } 351 352 // Test Load 1 353 // Restart the store (avoid cleaning up the files, to check the rebuilded trackers) 354 htu.getConfiguration().setBoolean(WALProcedureStore.EXEC_WAL_CLEANUP_ON_LOAD_CONF_KEY, false); 355 LoadCounter loader = new LoadCounter(); 356 storeRestart(loader); 357 assertEquals(1, loader.getLoadedCount()); 358 assertEquals(0, loader.getCorruptedCount()); 359 360 // Test Load 2 361 assertEquals(5, fs.listStatus(logDir).length); 362 loader = new LoadCounter(); 363 storeRestart(loader); 364 assertEquals(1, loader.getLoadedCount()); 365 assertEquals(0, loader.getCorruptedCount()); 366 367 // remove proc-0 368 procStore.delete(proc0.getProcId()); 369 procStore.periodicRollForTesting(); 370 assertEquals(1, fs.listStatus(logDir).length); 371 storeRestart(loader); 372 } 373 374 @Test 375 public void testProcIdHoles() throws Exception { 376 // Insert 377 for (int i = 0; i < 100; i += 2) { 378 procStore.insert(new TestProcedure(i), null); 379 if (i > 0 && (i % 10) == 0) { 380 LoadCounter loader = new LoadCounter(); 381 storeRestart(loader); 382 assertEquals(0, loader.getCorruptedCount()); 383 assertEquals((i / 2) + 1, loader.getLoadedCount()); 384 } 385 } 386 assertEquals(10, procStore.getActiveLogs().size()); 387 388 // Delete 389 for (int i = 0; i < 100; i += 2) { 390 procStore.delete(i); 391 } 392 assertEquals(1, procStore.getActiveLogs().size()); 393 394 LoadCounter loader = new LoadCounter(); 395 storeRestart(loader); 396 assertEquals(0, loader.getLoadedCount()); 397 assertEquals(0, loader.getCorruptedCount()); 398 } 399 400 @Test 401 public void testCorruptedTrailer() throws Exception { 402 // Insert something 403 for (int i = 0; i < 100; ++i) { 404 procStore.insert(new TestSequentialProcedure(), null); 405 } 406 407 // Stop the store 408 procStore.stop(false); 409 410 // Remove 4 byte from the trailer 411 FileStatus[] logs = fs.listStatus(logDir); 412 assertEquals(1, logs.length); 413 corruptLog(logs[0], 4); 414 415 LoadCounter loader = new LoadCounter(); 416 storeRestart(loader); 417 assertEquals(100, loader.getLoadedCount()); 418 assertEquals(0, loader.getCorruptedCount()); 419 } 420 421 private static void assertUpdated(final ProcedureStoreTracker tracker, final Procedure<?>[] procs, 422 final int[] updatedProcs, final int[] nonUpdatedProcs) { 423 for (int index : updatedProcs) { 424 long procId = procs[index].getProcId(); 425 assertTrue("Procedure id : " + procId, tracker.isModified(procId)); 426 } 427 for (int index : nonUpdatedProcs) { 428 long procId = procs[index].getProcId(); 429 assertFalse("Procedure id : " + procId, tracker.isModified(procId)); 430 } 431 } 432 433 private static void assertDeleted(final ProcedureStoreTracker tracker, final Procedure<?>[] procs, 434 final int[] deletedProcs, final int[] nonDeletedProcs) { 435 for (int index : deletedProcs) { 436 long procId = procs[index].getProcId(); 437 assertEquals("Procedure id : " + procId, ProcedureStoreTracker.DeleteState.YES, 438 tracker.isDeleted(procId)); 439 } 440 for (int index : nonDeletedProcs) { 441 long procId = procs[index].getProcId(); 442 assertEquals("Procedure id : " + procId, ProcedureStoreTracker.DeleteState.NO, 443 tracker.isDeleted(procId)); 444 } 445 } 446 447 @Test 448 public void testCorruptedTrailersRebuild() throws Exception { 449 final Procedure<?>[] procs = new Procedure[6]; 450 for (int i = 0; i < procs.length; ++i) { 451 procs[i] = new TestSequentialProcedure(); 452 } 453 // Log State (I=insert, U=updated, D=delete) 454 // | log 1 | log 2 | log 3 | 455 // 0 | I, D | | | 456 // 1 | I | | | 457 // 2 | I | D | | 458 // 3 | I | U | | 459 // 4 | | I | D | 460 // 5 | | | I | 461 procStore.insert(procs[0], null); 462 procStore.insert(procs[1], null); 463 procStore.insert(procs[2], null); 464 procStore.insert(procs[3], null); 465 procStore.delete(procs[0].getProcId()); 466 procStore.rollWriterForTesting(); 467 procStore.delete(procs[2].getProcId()); 468 procStore.update(procs[3]); 469 procStore.insert(procs[4], null); 470 procStore.rollWriterForTesting(); 471 procStore.delete(procs[4].getProcId()); 472 procStore.insert(procs[5], null); 473 474 // Stop the store 475 procStore.stop(false); 476 477 // Remove 4 byte from the trailers 478 final FileStatus[] logs = fs.listStatus(logDir); 479 assertEquals(3, logs.length); 480 for (int i = 0; i < logs.length; ++i) { 481 corruptLog(logs[i], 4); 482 } 483 484 // Restart the store (avoid cleaning up the files, to check the rebuilded trackers) 485 htu.getConfiguration().setBoolean(WALProcedureStore.EXEC_WAL_CLEANUP_ON_LOAD_CONF_KEY, false); 486 final LoadCounter loader = new LoadCounter(); 487 storeRestart(loader); 488 assertEquals(3, loader.getLoadedCount()); // procs 1, 3 and 5 489 assertEquals(0, loader.getCorruptedCount()); 490 491 // Check the Trackers 492 final ArrayList<ProcedureWALFile> walFiles = procStore.getActiveLogs(); 493 LOG.info("WALs " + walFiles); 494 assertEquals(4, walFiles.size()); 495 LOG.info("Checking wal " + walFiles.get(0)); 496 assertUpdated(walFiles.get(0).getTracker(), procs, new int[] { 0, 1, 2, 3 }, 497 new int[] { 4, 5 }); 498 LOG.info("Checking wal " + walFiles.get(1)); 499 assertUpdated(walFiles.get(1).getTracker(), procs, new int[] { 2, 3, 4 }, 500 new int[] { 0, 1, 5 }); 501 LOG.info("Checking wal " + walFiles.get(2)); 502 assertUpdated(walFiles.get(2).getTracker(), procs, new int[] { 4, 5 }, 503 new int[] { 0, 1, 2, 3 }); 504 LOG.info("Checking global tracker "); 505 assertDeleted(procStore.getStoreTracker(), procs, new int[] { 0, 2, 4 }, new int[] { 1, 3, 5 }); 506 } 507 508 @Test 509 public void testCorruptedEntries() throws Exception { 510 // Insert something 511 for (int i = 0; i < 100; ++i) { 512 procStore.insert(new TestSequentialProcedure(), null); 513 } 514 515 // Stop the store 516 procStore.stop(false); 517 518 // Remove some byte from the log 519 // (enough to cut the trailer and corrupt some entries) 520 FileStatus[] logs = fs.listStatus(logDir); 521 assertEquals(1, logs.length); 522 corruptLog(logs[0], 1823); 523 524 LoadCounter loader = new LoadCounter(); 525 storeRestart(loader); 526 assertTrue(procStore.getCorruptedLogs() != null); 527 assertEquals(1, procStore.getCorruptedLogs().size()); 528 assertEquals(87, loader.getLoadedCount()); 529 assertEquals(0, loader.getCorruptedCount()); 530 } 531 532 @Test 533 public void testCorruptedProcedures() throws Exception { 534 // Insert root-procedures 535 TestProcedure[] rootProcs = new TestProcedure[10]; 536 for (int i = 1; i <= rootProcs.length; i++) { 537 rootProcs[i - 1] = new TestProcedure(i, 0); 538 procStore.insert(rootProcs[i - 1], null); 539 rootProcs[i - 1].addStackId(0); 540 procStore.update(rootProcs[i - 1]); 541 } 542 // insert root-child txn 543 procStore.rollWriterForTesting(); 544 for (int i = 1; i <= rootProcs.length; i++) { 545 TestProcedure b = new TestProcedure(rootProcs.length + i, i); 546 rootProcs[i - 1].addStackId(1); 547 procStore.insert(rootProcs[i - 1], new Procedure[] { b }); 548 } 549 // insert child updates 550 procStore.rollWriterForTesting(); 551 for (int i = 1; i <= rootProcs.length; i++) { 552 procStore.update(new TestProcedure(rootProcs.length + i, i)); 553 } 554 555 // Stop the store 556 procStore.stop(false); 557 558 // the first log was removed, 559 // we have insert-txn and updates in the others so everything is fine 560 FileStatus[] logs = fs.listStatus(logDir); 561 assertEquals(Arrays.toString(logs), 2, logs.length); 562 Arrays.sort(logs, new Comparator<FileStatus>() { 563 @Override 564 public int compare(FileStatus o1, FileStatus o2) { 565 return o1.getPath().getName().compareTo(o2.getPath().getName()); 566 } 567 }); 568 569 LoadCounter loader = new LoadCounter(); 570 storeRestart(loader); 571 assertEquals(rootProcs.length * 2, loader.getLoadedCount()); 572 assertEquals(0, loader.getCorruptedCount()); 573 574 // Remove the second log, we have lost all the root/parent references 575 fs.delete(logs[0].getPath(), false); 576 loader.reset(); 577 storeRestart(loader); 578 assertEquals(0, loader.getLoadedCount()); 579 assertEquals(rootProcs.length, loader.getCorruptedCount()); 580 for (Procedure<?> proc : loader.getCorrupted()) { 581 assertTrue(proc.toString(), proc.getParentProcId() <= rootProcs.length); 582 assertTrue(proc.toString(), 583 proc.getProcId() > rootProcs.length && proc.getProcId() <= (rootProcs.length * 2)); 584 } 585 } 586 587 @Test 588 public void testRollAndRemove() throws IOException { 589 // Insert something in the log 590 Procedure<?> proc1 = new TestSequentialProcedure(); 591 procStore.insert(proc1, null); 592 593 Procedure<?> proc2 = new TestSequentialProcedure(); 594 procStore.insert(proc2, null); 595 596 // roll the log, now we have 2 597 procStore.rollWriterForTesting(); 598 assertEquals(2, procStore.getActiveLogs().size()); 599 600 // everything will be up to date in the second log 601 // so we can remove the first one 602 procStore.update(proc1); 603 procStore.update(proc2); 604 assertEquals(1, procStore.getActiveLogs().size()); 605 606 // roll the log, now we have 2 607 procStore.rollWriterForTesting(); 608 assertEquals(2, procStore.getActiveLogs().size()); 609 610 // remove everything active 611 // so we can remove all the logs 612 procStore.delete(proc1.getProcId()); 613 procStore.delete(proc2.getProcId()); 614 assertEquals(1, procStore.getActiveLogs().size()); 615 } 616 617 @Test 618 public void testFileNotFoundDuringLeaseRecovery() throws IOException { 619 final TestProcedure[] procs = new TestProcedure[3]; 620 for (int i = 0; i < procs.length; ++i) { 621 procs[i] = new TestProcedure(i + 1, 0); 622 procStore.insert(procs[i], null); 623 } 624 procStore.rollWriterForTesting(); 625 for (int i = 0; i < procs.length; ++i) { 626 procStore.update(procs[i]); 627 procStore.rollWriterForTesting(); 628 } 629 procStore.stop(false); 630 631 FileStatus[] status = fs.listStatus(logDir); 632 assertEquals(procs.length + 1, status.length); 633 634 // simulate another active master removing the wals 635 procStore = new WALProcedureStore(htu.getConfiguration(), logDir, null, new LeaseRecovery() { 636 private int count = 0; 637 638 @Override 639 public void recoverFileLease(FileSystem fs, Path path) throws IOException { 640 if (++count <= 2) { 641 fs.delete(path, false); 642 LOG.debug("Simulate FileNotFound at count=" + count + " for " + path); 643 throw new FileNotFoundException("test file not found " + path); 644 } 645 LOG.debug("Simulate recoverFileLease() at count=" + count + " for " + path); 646 } 647 }); 648 649 final LoadCounter loader = new LoadCounter(); 650 procStore.start(PROCEDURE_STORE_SLOTS); 651 procStore.recoverLease(); 652 procStore.load(loader); 653 assertEquals(procs.length, loader.getMaxProcId()); 654 assertEquals(1, loader.getRunnableCount()); 655 assertEquals(0, loader.getCompletedCount()); 656 assertEquals(0, loader.getCorruptedCount()); 657 } 658 659 @Test 660 public void testLogFileAlreadyExists() throws IOException { 661 final boolean[] tested = { false }; 662 WALProcedureStore mStore = Mockito.spy(procStore); 663 664 Answer<Boolean> ans = new Answer<Boolean>() { 665 @Override 666 public Boolean answer(InvocationOnMock invocationOnMock) throws Throwable { 667 long logId = ((Long) invocationOnMock.getArgument(0)).longValue(); 668 switch ((int) logId) { 669 case 2: 670 // Create a file so that real rollWriter() runs into file exists condition 671 Path logFilePath = mStore.getLogFilePath(logId); 672 mStore.getFileSystem().create(logFilePath); 673 break; 674 case 3: 675 // Success only when we retry with logId 3 676 tested[0] = true; 677 default: 678 break; 679 } 680 return (Boolean) invocationOnMock.callRealMethod(); 681 } 682 }; 683 684 // First time Store has one log file, next id will be 2 685 Mockito.doAnswer(ans).when(mStore).rollWriter(2); 686 // next time its 3 687 Mockito.doAnswer(ans).when(mStore).rollWriter(3); 688 689 mStore.recoverLease(); 690 assertTrue(tested[0]); 691 } 692 693 @Test 694 public void testLoadChildren() throws Exception { 695 TestProcedure a = new TestProcedure(1, 0); 696 TestProcedure b = new TestProcedure(2, 1); 697 TestProcedure c = new TestProcedure(3, 1); 698 699 // INIT 700 procStore.insert(a, null); 701 702 // Run A first step 703 a.addStackId(0); 704 procStore.update(a); 705 706 // Run A second step 707 a.addStackId(1); 708 procStore.insert(a, new Procedure[] { b, c }); 709 710 // Run B first step 711 b.addStackId(2); 712 procStore.update(b); 713 714 // Run C first and last step 715 c.addStackId(3); 716 procStore.update(c); 717 718 // Run B second setp 719 b.addStackId(4); 720 procStore.update(b); 721 722 // back to A 723 a.addStackId(5); 724 a.setSuccessState(); 725 procStore.delete(a, new long[] { b.getProcId(), c.getProcId() }); 726 restartAndAssert(3, 0, 1, 0); 727 } 728 729 @Test 730 public void testBatchDelete() throws Exception { 731 for (int i = 1; i < 10; ++i) { 732 procStore.insert(new TestProcedure(i), null); 733 } 734 735 // delete nothing 736 long[] toDelete = new long[] { 1, 2, 3, 4 }; 737 procStore.delete(toDelete, 2, 0); 738 LoadCounter loader = restartAndAssert(9, 9, 0, 0); 739 for (int i = 1; i < 10; ++i) { 740 assertEquals(true, loader.isRunnable(i)); 741 } 742 743 // delete the full "toDelete" array (2, 4, 6, 8) 744 toDelete = new long[] { 2, 4, 6, 8 }; 745 procStore.delete(toDelete, 0, toDelete.length); 746 loader = restartAndAssert(9, 5, 0, 0); 747 for (int i = 1; i < 10; ++i) { 748 assertEquals(i % 2 != 0, loader.isRunnable(i)); 749 } 750 751 // delete a slice of "toDelete" (1, 3) 752 toDelete = new long[] { 5, 7, 1, 3, 9 }; 753 procStore.delete(toDelete, 2, 2); 754 loader = restartAndAssert(9, 3, 0, 0); 755 for (int i = 1; i < 10; ++i) { 756 assertEquals(i > 3 && i % 2 != 0, loader.isRunnable(i)); 757 } 758 759 // delete a single item (5) 760 toDelete = new long[] { 5 }; 761 procStore.delete(toDelete, 0, 1); 762 loader = restartAndAssert(9, 2, 0, 0); 763 for (int i = 1; i < 10; ++i) { 764 assertEquals(i > 5 && i % 2 != 0, loader.isRunnable(i)); 765 } 766 767 // delete remaining using a slice of "toDelete" (7, 9) 768 toDelete = new long[] { 0, 7, 9 }; 769 procStore.delete(toDelete, 1, 2); 770 loader = restartAndAssert(0, 0, 0, 0); 771 for (int i = 1; i < 10; ++i) { 772 assertEquals(false, loader.isRunnable(i)); 773 } 774 } 775 776 @Test 777 public void testBatchInsert() throws Exception { 778 final int count = 10; 779 final TestProcedure[] procs = new TestProcedure[count]; 780 for (int i = 0; i < procs.length; ++i) { 781 procs[i] = new TestProcedure(i + 1); 782 } 783 procStore.insert(procs); 784 restartAndAssert(count, count, 0, 0); 785 786 for (int i = 0; i < procs.length; ++i) { 787 final long procId = procs[i].getProcId(); 788 procStore.delete(procId); 789 restartAndAssert(procId != count ? count : 0, count - (i + 1), 0, 0); 790 } 791 procStore.removeInactiveLogsForTesting(); 792 assertEquals("WALs=" + procStore.getActiveLogs(), 1, procStore.getActiveLogs().size()); 793 } 794 795 @Test 796 public void testWALDirAndWALArchiveDir() throws IOException { 797 Configuration conf = htu.getConfiguration(); 798 procStore = createWALProcedureStore(conf); 799 assertEquals(procStore.getFileSystem(), procStore.getWalArchiveDir().getFileSystem(conf)); 800 } 801 802 private WALProcedureStore createWALProcedureStore(Configuration conf) throws IOException { 803 return new WALProcedureStore(conf, new LeaseRecovery() { 804 @Override 805 public void recoverFileLease(FileSystem fs, Path path) throws IOException { 806 // no-op 807 } 808 }); 809 } 810 811 private LoadCounter restartAndAssert(long maxProcId, long runnableCount, int completedCount, 812 int corruptedCount) throws Exception { 813 return ProcedureTestingUtility.storeRestartAndAssert(procStore, maxProcId, runnableCount, 814 completedCount, corruptedCount); 815 } 816 817 private void corruptLog(final FileStatus logFile, final long dropBytes) throws IOException { 818 assertTrue(logFile.getLen() > dropBytes); 819 LOG.debug( 820 "corrupt log " + logFile.getPath() + " size=" + logFile.getLen() + " drop=" + dropBytes); 821 Path tmpPath = new Path(testDir, "corrupted.log"); 822 InputStream in = fs.open(logFile.getPath()); 823 OutputStream out = fs.create(tmpPath); 824 IOUtils.copyBytes(in, out, logFile.getLen() - dropBytes, true); 825 if (!fs.rename(tmpPath, logFile.getPath())) { 826 throw new IOException("Unable to rename"); 827 } 828 } 829 830 private void verifyProcIdsOnRestart(final Set<Long> procIds) throws Exception { 831 LOG.debug("expected: " + procIds); 832 LoadCounter loader = new LoadCounter(); 833 storeRestart(loader); 834 assertEquals(procIds.size(), loader.getLoadedCount()); 835 assertEquals(0, loader.getCorruptedCount()); 836 } 837 838 public static class TestSequentialProcedure extends SequentialProcedure<Void> { 839 840 private static final AtomicLong seqId = new AtomicLong(0); 841 842 public TestSequentialProcedure() { 843 setProcId(seqId.incrementAndGet()); 844 } 845 846 @Override 847 protected Procedure<Void>[] execute(Void env) { 848 return null; 849 } 850 851 @Override 852 protected void rollback(Void env) { 853 } 854 855 @Override 856 protected boolean abort(Void env) { 857 return false; 858 } 859 860 @Override 861 protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException { 862 long procId = getProcId(); 863 if (procId % 2 == 0) { 864 Int64Value.Builder builder = Int64Value.newBuilder().setValue(procId); 865 serializer.serialize(builder.build()); 866 } 867 } 868 869 @Override 870 protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException { 871 long procId = getProcId(); 872 if (procId % 2 == 0) { 873 Int64Value value = serializer.deserialize(Int64Value.class); 874 assertEquals(procId, value.getValue()); 875 } 876 } 877 } 878}