|
48 | 48 | import java.nio.channels.FileChannel; |
49 | 49 | import java.nio.charset.Charset; |
50 | 50 | import java.util.ArrayList; |
| 51 | +import java.util.Collection; |
51 | 52 | import java.util.HashSet; |
52 | 53 | import java.util.LinkedList; |
53 | 54 | import java.util.List; |
|
65 | 66 | import java.util.concurrent.locks.Lock; |
66 | 67 | import org.apache.bookkeeper.bookie.DefaultEntryLogger.BufferedLogChannel; |
67 | 68 | import org.apache.bookkeeper.bookie.LedgerDirsManager.NoWritableLedgerDirException; |
| 69 | +import org.apache.bookkeeper.bookie.storage.CompactionEntryLog; |
| 70 | +import org.apache.bookkeeper.bookie.storage.EntryLogScanner; |
68 | 71 | import org.apache.bookkeeper.common.testing.annotations.FlakyTest; |
69 | 72 | import org.apache.bookkeeper.conf.ServerConfiguration; |
70 | 73 | import org.apache.bookkeeper.conf.TestBKConfiguration; |
71 | 74 | import org.apache.bookkeeper.test.TestStatsProvider; |
72 | 75 | import org.apache.bookkeeper.util.DiskChecker; |
| 76 | +import org.apache.bookkeeper.util.HardLink; |
73 | 77 | import org.apache.bookkeeper.util.IOUtils; |
74 | 78 | import org.apache.bookkeeper.util.collections.ConcurrentLongLongHashMap; |
75 | 79 | import org.apache.commons.io.FileUtils; |
|
87 | 91 | @TestMethodOrder(MethodOrderer.MethodName.class) |
88 | 92 | public class DefaultEntryLogTest { |
89 | 93 | private static final Logger LOG = LoggerFactory.getLogger(DefaultEntryLogTest.class); |
| 94 | + private static final long COMPACTION_SOURCE_LOG_ID = 1L; |
| 95 | + private static final long COMPACTION_PREVIOUS_LOG_ID = 499L; |
| 96 | + private static final long COMPACTION_LOG_ID = COMPACTION_PREVIOUS_LOG_ID + 1; |
| 97 | + private static final long COMPACTION_LEDGER_ID = 1234L; |
| 98 | + private static final long COMPACTION_ENTRY_ID = 5L; |
| 99 | + private static final String HEX_FINAL_LOG_FILE_NAME = "1f4.log"; |
| 100 | + private static final String DECIMAL_FINAL_LOG_FILE_NAME = "500.log"; |
90 | 101 |
|
91 | 102 | final List<File> tempDirs = new ArrayList<File>(); |
92 | 103 | final Random rand = new Random(); |
@@ -983,6 +994,138 @@ public void testReadEntryWithoutLedgerID() throws Exception { |
983 | 994 | } |
984 | 995 | } |
985 | 996 |
|
| 997 | + @Test |
| 998 | + public void testRecoverCompactedLogUsesHexFinalLogFileName() throws Exception { |
| 999 | + ByteBuf entry = generateEntry(COMPACTION_LEDGER_ID, COMPACTION_ENTRY_ID); |
| 1000 | + try { |
| 1001 | + prepareCompactedLog(entry); |
| 1002 | + |
| 1003 | + CompactionEntryLog recoveredLog = recoverSingleCompactionLog(); |
| 1004 | + |
| 1005 | + // Recovery creates the final entry log in makeAvailable(). It must create |
| 1006 | + // the normal hex entry log name, not the decimal name from the parsed id. |
| 1007 | + recoveredLog.makeAvailable(); |
| 1008 | + assertTrue(hexFinalLogFile().exists(), "Recovery should create the hex-named final log"); |
| 1009 | + assertFalse(decimalFinalLogFile().exists(), "Recovery should not create a decimal-named final log"); |
| 1010 | + assertCompactionLogContainsEntry(recoveredLog); |
| 1011 | + } finally { |
| 1012 | + ReferenceCountUtil.release(entry); |
| 1013 | + } |
| 1014 | + } |
| 1015 | + |
| 1016 | + @Test |
| 1017 | + public void testRecoverCompactedLogWithStaleDecimalLogFileFromOldRecovery() throws Exception { |
| 1018 | + ByteBuf entry = generateEntry(COMPACTION_LEDGER_ID, COMPACTION_ENTRY_ID); |
| 1019 | + try { |
| 1020 | + prepareCompactedLog(entry); |
| 1021 | + |
| 1022 | + // Simulate an upgrade after the old buggy recovery already created |
| 1023 | + // "500.log", while the ".compacted" marker is still present. |
| 1024 | + HardLink.createHardLink(compactedFile(), decimalFinalLogFile()); |
| 1025 | + assertTrue(decimalFinalLogFile().exists(), "Stale decimal log should exist before fixed recovery"); |
| 1026 | + |
| 1027 | + CompactionEntryLog recoveredLog = recoverSingleCompactionLog(); |
| 1028 | + recoveredLog.makeAvailable(); |
| 1029 | + |
| 1030 | + assertTrue(hexFinalLogFile().exists(), "Fixed recovery should create the hex-named final log"); |
| 1031 | + assertTrue(decimalFinalLogFile().exists(), "Fixed recovery should leave the stale file untouched"); |
| 1032 | + assertCompactionLogContainsEntry(recoveredLog); |
| 1033 | + |
| 1034 | + recoveredLog.finalizeAndCleanup(); |
| 1035 | + assertFalse(compactedFile().exists(), "Successful recovery cleanup should remove the marker"); |
| 1036 | + assertTrue(decimalFinalLogFile().exists(), "Cleanup should not remove unrelated stale decimal logs"); |
| 1037 | + } finally { |
| 1038 | + ReferenceCountUtil.release(entry); |
| 1039 | + } |
| 1040 | + } |
| 1041 | + |
| 1042 | + @Test |
| 1043 | + public void testNewCompactionLogKeepsHexFinalLogFileName() throws Exception { |
| 1044 | + ByteBuf entry = generateEntry(COMPACTION_LEDGER_ID, COMPACTION_ENTRY_ID); |
| 1045 | + try { |
| 1046 | + CompactionEntryLog compactionLog = prepareCompactedLog(entry); |
| 1047 | + |
| 1048 | + // Normal compaction derives finalLogFile from the compacting file name, |
| 1049 | + // so makeAvailable() should create "1f4.log", not "500.log". |
| 1050 | + compactionLog.makeAvailable(); |
| 1051 | + assertTrue(hexFinalLogFile().exists(), "Normal compaction should create the hex-named final log"); |
| 1052 | + assertFalse(decimalFinalLogFile().exists(), "Normal compaction should not create a decimal-named log"); |
| 1053 | + assertCompactionLogContainsEntry(compactionLog); |
| 1054 | + } finally { |
| 1055 | + ReferenceCountUtil.release(entry); |
| 1056 | + } |
| 1057 | + } |
| 1058 | + |
| 1059 | + private CompactionEntryLog prepareCompactedLog(ByteBuf entry) throws Exception { |
| 1060 | + // Force the next compaction log id to 500, whose entry log file name is "1f4.log". |
| 1061 | + resetEntryLoggerWithLastLogId(COMPACTION_PREVIOUS_LOG_ID); |
| 1062 | + |
| 1063 | + CompactionEntryLog compactionLog = entryLogger.newCompactionLog(COMPACTION_SOURCE_LOG_ID); |
| 1064 | + assertEquals(COMPACTION_LOG_ID, compactionLog.getDstLogId()); |
| 1065 | + compactionLog.addEntry(COMPACTION_LEDGER_ID, entry); |
| 1066 | + compactionLog.flush(); |
| 1067 | + |
| 1068 | + // Leave "<dst-hex>.log.<src-hex>.compacted", as if the bookie restarted |
| 1069 | + // after markCompacted() but before makeAvailable()/index recovery completed. |
| 1070 | + compactionLog.markCompacted(); |
| 1071 | + |
| 1072 | + assertTrue(compactedFile().exists(), "Compacted recovery marker should exist"); |
| 1073 | + assertFalse(hexFinalLogFile().exists(), "Final log file should not exist before makeAvailable"); |
| 1074 | + |
| 1075 | + return compactionLog; |
| 1076 | + } |
| 1077 | + |
| 1078 | + private CompactionEntryLog recoverSingleCompactionLog() { |
| 1079 | + Collection<CompactionEntryLog> incompleteLogs = entryLogger.incompleteCompactionLogs(); |
| 1080 | + assertEquals(1, incompleteLogs.size()); |
| 1081 | + |
| 1082 | + CompactionEntryLog recoveredLog = incompleteLogs.iterator().next(); |
| 1083 | + assertEquals(COMPACTION_LOG_ID, recoveredLog.getDstLogId()); |
| 1084 | + assertEquals(COMPACTION_SOURCE_LOG_ID, recoveredLog.getSrcLogId()); |
| 1085 | + return recoveredLog; |
| 1086 | + } |
| 1087 | + |
| 1088 | + private File compactedFile() { |
| 1089 | + return new File(curDir, HEX_FINAL_LOG_FILE_NAME + "." |
| 1090 | + + DefaultEntryLogger.logId2HexString(COMPACTION_SOURCE_LOG_ID) |
| 1091 | + + TransactionalEntryLogCompactor.COMPACTED_SUFFIX); |
| 1092 | + } |
| 1093 | + |
| 1094 | + private File hexFinalLogFile() { |
| 1095 | + return new File(curDir, HEX_FINAL_LOG_FILE_NAME); |
| 1096 | + } |
| 1097 | + |
| 1098 | + private File decimalFinalLogFile() { |
| 1099 | + return new File(curDir, DECIMAL_FINAL_LOG_FILE_NAME); |
| 1100 | + } |
| 1101 | + |
| 1102 | + private void assertCompactionLogContainsEntry(CompactionEntryLog compactionLog) throws Exception { |
| 1103 | + AtomicBoolean foundEntry = new AtomicBoolean(false); |
| 1104 | + // Scanning uses the compaction log id to resolve the final log file. This |
| 1105 | + // catches cases where makeAvailable() created a file under the wrong name. |
| 1106 | + compactionLog.scan(new EntryLogScanner() { |
| 1107 | + @Override |
| 1108 | + public boolean accept(long ledgerId) { |
| 1109 | + return true; |
| 1110 | + } |
| 1111 | + |
| 1112 | + @Override |
| 1113 | + public void process(long ledgerIdFromScanner, long offset, ByteBuf recoveredEntry) { |
| 1114 | + assertEquals(COMPACTION_LEDGER_ID, ledgerIdFromScanner); |
| 1115 | + assertEquals(COMPACTION_LEDGER_ID, recoveredEntry.getLong(recoveredEntry.readerIndex())); |
| 1116 | + assertEquals(COMPACTION_ENTRY_ID, recoveredEntry.getLong(recoveredEntry.readerIndex() + Long.BYTES)); |
| 1117 | + foundEntry.set(true); |
| 1118 | + } |
| 1119 | + }); |
| 1120 | + assertTrue(foundEntry.get(), "Compacted log should be readable through the compaction log id"); |
| 1121 | + } |
| 1122 | + |
| 1123 | + private void resetEntryLoggerWithLastLogId(long lastLogId) throws Exception { |
| 1124 | + entryLogger.getEntryLoggerAllocator().setLastLogId(curDir, lastLogId); |
| 1125 | + entryLogger.close(); |
| 1126 | + entryLogger = new DefaultEntryLogger(conf, dirsMgr); |
| 1127 | + } |
| 1128 | + |
986 | 1129 |
|
987 | 1130 | /* |
988 | 1131 | * tests basic logic of EntryLogManager interface for |
|
0 commit comments