diff --git a/bin/zkTxnLogToolkit.cmd b/bin/zkTxnLogToolkit.cmd new file mode 100755 index 00000000000..362dc44b027 --- /dev/null +++ b/bin/zkTxnLogToolkit.cmd @@ -0,0 +1,24 @@ +@echo off +REM Licensed to the Apache Software Foundation (ASF) under one or more +REM contributor license agreements. See the NOTICE file distributed with +REM this work for additional information regarding copyright ownership. +REM The ASF licenses this file to You under the Apache License, Version 2.0 +REM (the "License"); you may not use this file except in compliance with +REM the License. You may obtain a copy of the License at +REM +REM http://www.apache.org/licenses/LICENSE-2.0 +REM +REM Unless required by applicable law or agreed to in writing, software +REM distributed under the License is distributed on an "AS IS" BASIS, +REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +REM See the License for the specific language governing permissions and +REM limitations under the License. + +setlocal +call "%~dp0zkEnv.cmd" + +set ZOOMAIN=org.apache.zookeeper.server.persistence.TxnLogToolkit +call %JAVA% -cp "%CLASSPATH%" %ZOOMAIN% %* + +endlocal + diff --git a/bin/zkTxnLogToolkit.sh b/bin/zkTxnLogToolkit.sh new file mode 100755 index 00000000000..8beed20ddd7 --- /dev/null +++ b/bin/zkTxnLogToolkit.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# If this scripted is run out of /usr/bin or some other system bin directory +# it should be linked to and not copied. Things like java jar files are found +# relative to the canonical path of this script. +# + +# use POSIX interface, symlink is followed automatically +ZOOBIN="${BASH_SOURCE-$0}" +ZOOBIN="$(dirname "${ZOOBIN}")" +ZOOBINDIR="$(cd "${ZOOBIN}"; pwd)" + +if [ -e "$ZOOBIN/../libexec/zkEnv.sh" ]; then + . "$ZOOBINDIR"/../libexec/zkEnv.sh +else + . "$ZOOBINDIR"/zkEnv.sh +fi + +"$JAVA" -cp "$CLASSPATH" $JVMFLAGS \ + org.apache.zookeeper.server.persistence.TxnLogToolkit "$@" + + diff --git a/docs/bookkeeperConfig.pdf b/docs/bookkeeperConfig.pdf index c62f4933a0d..07b8b9c573b 100644 Binary files a/docs/bookkeeperConfig.pdf and b/docs/bookkeeperConfig.pdf differ diff --git a/docs/bookkeeperOverview.pdf b/docs/bookkeeperOverview.pdf index 25880ad63a1..58c3b81dd32 100644 Binary files a/docs/bookkeeperOverview.pdf and b/docs/bookkeeperOverview.pdf differ diff --git a/docs/bookkeeperProgrammer.pdf b/docs/bookkeeperProgrammer.pdf index dbf3cb61a5c..efe127b46d6 100644 Binary files a/docs/bookkeeperProgrammer.pdf and b/docs/bookkeeperProgrammer.pdf differ diff --git a/docs/bookkeeperStarted.pdf b/docs/bookkeeperStarted.pdf index 483d9f74a8c..bca40a5efc2 100644 Binary files a/docs/bookkeeperStarted.pdf and b/docs/bookkeeperStarted.pdf differ diff --git a/docs/bookkeeperStream.pdf b/docs/bookkeeperStream.pdf index 5b5ca786d87..7d602ddbfaf 100644 Binary files a/docs/bookkeeperStream.pdf and b/docs/bookkeeperStream.pdf differ diff --git a/docs/index.pdf b/docs/index.pdf index bd53a29c28d..de75668548e 100644 Binary files a/docs/index.pdf and b/docs/index.pdf differ diff --git a/docs/javaExample.pdf b/docs/javaExample.pdf index f34daa4e8c7..a5a2e745d83 100644 Binary files a/docs/javaExample.pdf and b/docs/javaExample.pdf differ diff --git a/docs/linkmap.pdf b/docs/linkmap.pdf index 0f8bcad2efe..d756b22e095 100644 Binary files a/docs/linkmap.pdf and b/docs/linkmap.pdf differ diff --git a/docs/recipes.pdf b/docs/recipes.pdf index 5aab9ad0e84..3c4cb9e8476 100644 Binary files a/docs/recipes.pdf and b/docs/recipes.pdf differ diff --git a/docs/zookeeperAdmin.html b/docs/zookeeperAdmin.html index f8166f9f8b7..9f00dba81de 100644 --- a/docs/zookeeperAdmin.html +++ b/docs/zookeeperAdmin.html @@ -316,6 +316,9 @@

A Guide to Deployment and Administration

  • File Management
  • +
  • +Recovery - TxnLogToolkit +
  • @@ -2063,6 +2066,65 @@

    File Management

    + +

    Recovery - TxnLogToolkit

    +

    TxnLogToolkit is a command line tool shipped with ZooKeeper which + is capable of recovering transaction log entries with broken CRC.

    +

    Running it without any command line parameters or with the "-h,--help" + argument, it outputs the following help page:

    +
    +          $ bin/zkTxnLogToolkit.sh
    +
    +          usage: TxnLogToolkit [-dhrv] txn_log_file_name
    +          -d,--dump      Dump mode. Dump all entries of the log file. (this is the default)
    +          -h,--help      Print help message
    +          -r,--recover   Recovery mode. Re-calculate CRC for broken entries.
    +          -v,--verbose   Be verbose in recovery mode: print all entries, not just fixed ones.
    +          -y,--yes       Non-interactive mode: repair all CRC errors without asking
    +        
    +

    The default behaviour is safe: it dumps the entries of the given + transaction log file to the screen: (same as using '-d,--dump' parameter)

    +
    +          $ bin/zkTxnLogToolkit.sh log.100000001
    +          ZooKeeper Transactional Log File with dbid 0 txnlog format version 2
    +          4/5/18 2:15:58 PM CEST session 0x16295bafcc40000 cxid 0x0 zxid 0x100000001 createSession 30000
    +          CRC ERROR - 4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null
    +          4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null
    +          4/5/18 2:16:12 PM CEST session 0x26295bafcc90000 cxid 0x0 zxid 0x100000003 createSession 30000
    +          4/5/18 2:17:34 PM CEST session 0x26295bafcc90000 cxid 0x0 zxid 0x200000001 closeSession null
    +          4/5/18 2:17:34 PM CEST session 0x16295bd23720000 cxid 0x0 zxid 0x200000002 createSession 30000
    +          4/5/18 2:18:02 PM CEST session 0x16295bd23720000 cxid 0x2 zxid 0x200000003 create '/andor,#626262,v{s{31,s{'world,'anyone}}},F,1
    +          EOF reached after 6 txns.
    +        
    +

    There's a CRC error in the 2nd entry of the above transaction log file. In dump + mode, the toolkit only prints this information to the screen without touching the original file. In + recovery mode (-r,--recover flag) the original file still remains + untouched and all transactions will be copied over to a new txn log file with ".fixed" suffix. It recalculates + CRC values and copies the calculated value, if it doesn't match the original txn entry. + By default, the tool works interactively: it asks for confirmation whenever CRC error encountered.

    +
    +          $ bin/zkTxnLogToolkit.sh -r log.100000001
    +          ZooKeeper Transactional Log File with dbid 0 txnlog format version 2
    +          CRC ERROR - 4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null
    +          Would you like to fix it (Yes/No/Abort) ?
    +        
    +

    Answering Yes means the newly calculated CRC value will be outputted + to the new file. No means that the original CRC value will be copied over. + Abort will abort the entire operation and exits. + (In this case the ".fixed" will not be deleted and left in a half-complete state: contains only entries which + have already been processed or only the header if the operation was aborted at the first entry.)

    +
    +          $ bin/zkTxnLogToolkit.sh -r log.100000001
    +          ZooKeeper Transactional Log File with dbid 0 txnlog format version 2
    +          CRC ERROR - 4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null
    +          Would you like to fix it (Yes/No/Abort) ? y
    +          EOF reached after 6 txns.
    +          Recovery file log.100000001.fixed has been written with 1 fixed CRC error(s)
    +        
    +

    The default behaviour of recovery is to be silent: only entries with CRC error get printed to the screen. + One can turn on verbose mode with the -v,--verbose parameter to see all records. + Interactive mode can be turned off with the -y,--yes parameter. In this case all CRC errors will be fixed + in the new transaction file.

    Things to Avoid

    Here are some common problems you can avoid by configuring diff --git a/docs/zookeeperAdmin.pdf b/docs/zookeeperAdmin.pdf index 0004649cd56..21728be6547 100644 Binary files a/docs/zookeeperAdmin.pdf and b/docs/zookeeperAdmin.pdf differ diff --git a/docs/zookeeperHierarchicalQuorums.pdf b/docs/zookeeperHierarchicalQuorums.pdf index f86909f1a83..d0db17fdef9 100644 Binary files a/docs/zookeeperHierarchicalQuorums.pdf and b/docs/zookeeperHierarchicalQuorums.pdf differ diff --git a/docs/zookeeperInternals.pdf b/docs/zookeeperInternals.pdf index 8bcd192f783..7ca1e0bba77 100644 Binary files a/docs/zookeeperInternals.pdf and b/docs/zookeeperInternals.pdf differ diff --git a/docs/zookeeperJMX.pdf b/docs/zookeeperJMX.pdf index 8b74b239eb7..56d67e10daa 100644 Binary files a/docs/zookeeperJMX.pdf and b/docs/zookeeperJMX.pdf differ diff --git a/docs/zookeeperObservers.pdf b/docs/zookeeperObservers.pdf index 29ebbb029d2..f793f34b248 100644 Binary files a/docs/zookeeperObservers.pdf and b/docs/zookeeperObservers.pdf differ diff --git a/docs/zookeeperOver.pdf b/docs/zookeeperOver.pdf index 8839eef6474..c26219781d9 100644 Binary files a/docs/zookeeperOver.pdf and b/docs/zookeeperOver.pdf differ diff --git a/docs/zookeeperProgrammers.pdf b/docs/zookeeperProgrammers.pdf index 8448d313040..b375fafd4ee 100644 Binary files a/docs/zookeeperProgrammers.pdf and b/docs/zookeeperProgrammers.pdf differ diff --git a/docs/zookeeperQuotas.pdf b/docs/zookeeperQuotas.pdf index 6a95063ed0d..d6894c61c41 100644 Binary files a/docs/zookeeperQuotas.pdf and b/docs/zookeeperQuotas.pdf differ diff --git a/docs/zookeeperStarted.pdf b/docs/zookeeperStarted.pdf index e6e15ccc4b4..cb52a80b6ba 100644 Binary files a/docs/zookeeperStarted.pdf and b/docs/zookeeperStarted.pdf differ diff --git a/docs/zookeeperTutorial.pdf b/docs/zookeeperTutorial.pdf index 70df2639583..543177c8545 100644 Binary files a/docs/zookeeperTutorial.pdf and b/docs/zookeeperTutorial.pdf differ diff --git a/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml b/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml index 447b90c364d..2f3a57fac86 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml @@ -1702,6 +1702,76 @@ imok individual settings in which it is being deployed. + +

    + Recovery - TxnLogToolkit + + TxnLogToolkit is a command line tool shipped with ZooKeeper which + is capable of recovering transaction log entries with broken CRC. + Running it without any command line parameters or with the "-h,--help" + argument, it outputs the following help page: + + + $ bin/zkTxnLogToolkit.sh + + usage: TxnLogToolkit [-dhrv] txn_log_file_name + -d,--dump Dump mode. Dump all entries of the log file. (this is the default) + -h,--help Print help message + -r,--recover Recovery mode. Re-calculate CRC for broken entries. + -v,--verbose Be verbose in recovery mode: print all entries, not just fixed ones. + -y,--yes Non-interactive mode: repair all CRC errors without asking + + + The default behaviour is safe: it dumps the entries of the given + transaction log file to the screen: (same as using '-d,--dump' parameter) + + + $ bin/zkTxnLogToolkit.sh log.100000001 + ZooKeeper Transactional Log File with dbid 0 txnlog format version 2 + 4/5/18 2:15:58 PM CEST session 0x16295bafcc40000 cxid 0x0 zxid 0x100000001 createSession 30000 + CRC ERROR - 4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null + 4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null + 4/5/18 2:16:12 PM CEST session 0x26295bafcc90000 cxid 0x0 zxid 0x100000003 createSession 30000 + 4/5/18 2:17:34 PM CEST session 0x26295bafcc90000 cxid 0x0 zxid 0x200000001 closeSession null + 4/5/18 2:17:34 PM CEST session 0x16295bd23720000 cxid 0x0 zxid 0x200000002 createSession 30000 + 4/5/18 2:18:02 PM CEST session 0x16295bd23720000 cxid 0x2 zxid 0x200000003 create '/andor,#626262,v{s{31,s{'world,'anyone}}},F,1 + EOF reached after 6 txns. + + + There's a CRC error in the 2nd entry of the above transaction log file. In dump + mode, the toolkit only prints this information to the screen without touching the original file. In + recovery mode (-r,--recover flag) the original file still remains + untouched and all transactions will be copied over to a new txn log file with ".fixed" suffix. It recalculates + CRC values and copies the calculated value, if it doesn't match the original txn entry. + By default, the tool works interactively: it asks for confirmation whenever CRC error encountered. + + + $ bin/zkTxnLogToolkit.sh -r log.100000001 + ZooKeeper Transactional Log File with dbid 0 txnlog format version 2 + CRC ERROR - 4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null + Would you like to fix it (Yes/No/Abort) ? + + + Answering Yes means the newly calculated CRC value will be outputted + to the new file. No means that the original CRC value will be copied over. + Abort will abort the entire operation and exits. + (In this case the ".fixed" will not be deleted and left in a half-complete state: contains only entries which + have already been processed or only the header if the operation was aborted at the first entry.) + + + $ bin/zkTxnLogToolkit.sh -r log.100000001 + ZooKeeper Transactional Log File with dbid 0 txnlog format version 2 + CRC ERROR - 4/5/18 2:16:05 PM CEST session 0x16295bafcc40000 cxid 0x1 zxid 0x100000002 closeSession null + Would you like to fix it (Yes/No/Abort) ? y + EOF reached after 6 txns. + Recovery file log.100000001.fixed has been written with 1 fixed CRC error(s) + + + The default behaviour of recovery is to be silent: only entries with CRC error get printed to the screen. + One can turn on verbose mode with the -v,--verbose parameter to see all records. + Interactive mode can be turned off with the -y,--yes parameter. In this case all CRC errors will be fixed + in the new transaction file. +
    diff --git a/src/java/main/org/apache/zookeeper/server/TraceFormatter.java b/src/java/main/org/apache/zookeeper/server/TraceFormatter.java index 60d1cc7580b..66cc31ca38b 100644 --- a/src/java/main/org/apache/zookeeper/server/TraceFormatter.java +++ b/src/java/main/org/apache/zookeeper/server/TraceFormatter.java @@ -29,7 +29,7 @@ public class TraceFormatter { - static String op2String(int op) { + public static String op2String(int op) { switch (op) { case OpCode.notification: return "notification"; diff --git a/src/java/main/org/apache/zookeeper/server/persistence/FilePadding.java b/src/java/main/org/apache/zookeeper/server/persistence/FilePadding.java new file mode 100644 index 00000000000..c4052e95843 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/persistence/FilePadding.java @@ -0,0 +1,105 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.persistence; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +public class FilePadding { + private static final Logger LOG; + private static long preAllocSize = 65536 * 1024; + private static final ByteBuffer fill = ByteBuffer.allocateDirect(1); + + static { + LOG = LoggerFactory.getLogger(FileTxnLog.class); + + String size = System.getProperty("zookeeper.preAllocSize"); + if (size != null) { + try { + preAllocSize = Long.parseLong(size) * 1024; + } catch (NumberFormatException e) { + LOG.warn(size + " is not a valid value for preAllocSize"); + } + } + } + + private long currentSize; + + /** + * method to allow setting preallocate size + * of log file to pad the file. + * + * @param size the size to set to in bytes + */ + public static void setPreallocSize(long size) { + preAllocSize = size; + } + + public void setCurrentSize(long currentSize) { + this.currentSize = currentSize; + } + + /** + * pad the current file to increase its size to the next multiple of preAllocSize greater than the current size and position + * + * @param fileChannel the fileChannel of the file to be padded + * @throws IOException + */ + long padFile(FileChannel fileChannel) throws IOException { + long newFileSize = calculateFileSizeWithPadding(fileChannel.position(), currentSize, preAllocSize); + if (currentSize != newFileSize) { + fileChannel.write((ByteBuffer) fill.position(0), newFileSize - fill.remaining()); + currentSize = newFileSize; + } + return currentSize; + } + + /** + * Calculates a new file size with padding. We only return a new size if + * the current file position is sufficiently close (less than 4K) to end of + * file and preAllocSize is > 0. + * + * @param position the point in the file we have written to + * @param fileSize application keeps track of the current file size + * @param preAllocSize how many bytes to pad + * @return the new file size. It can be the same as fileSize if no + * padding was done. + * @throws IOException + */ + // VisibleForTesting + public static long calculateFileSizeWithPadding(long position, long fileSize, long preAllocSize) { + // If preAllocSize is positive and we are within 4KB of the known end of the file calculate a new file size + if (preAllocSize > 0 && position + 4096 >= fileSize) { + // If we have written more than we have previously preallocated we need to make sure the new + // file size is larger than what we already have + if (position > fileSize) { + fileSize = position + preAllocSize; + fileSize -= fileSize % preAllocSize; + } else { + fileSize += preAllocSize; + } + } + + return fileSize; + } +} diff --git a/src/java/main/org/apache/zookeeper/server/persistence/FileTxnLog.java b/src/java/main/org/apache/zookeeper/server/persistence/FileTxnLog.java index 3694c985ec3..15c7211fa8d 100644 --- a/src/java/main/org/apache/zookeeper/server/persistence/FileTxnLog.java +++ b/src/java/main/org/apache/zookeeper/server/persistence/FileTxnLog.java @@ -91,9 +91,6 @@ public class FileTxnLog implements TxnLog { private static final Logger LOG; - static long preAllocSize = 65536 * 1024; - private static final ByteBuffer fill = ByteBuffer.allocateDirect(1); - public final static int TXNLOG_MAGIC = ByteBuffer.wrap("ZKLG".getBytes()).getInt(); @@ -107,14 +104,6 @@ public class FileTxnLog implements TxnLog { static { LOG = LoggerFactory.getLogger(FileTxnLog.class); - String size = System.getProperty("zookeeper.preAllocSize"); - if (size != null) { - try { - preAllocSize = Long.parseLong(size) * 1024; - } catch (NumberFormatException e) { - LOG.warn(size + " is not a valid value for preAllocSize"); - } - } /** Local variable to read fsync.warningthresholdms into */ Long fsyncWarningThreshold; if ((fsyncWarningThreshold = Long.getLong("zookeeper.fsync.warningthresholdms")) == null) @@ -132,8 +121,8 @@ public class FileTxnLog implements TxnLog { long dbId; private LinkedList streamsToFlush = new LinkedList(); - long currentSize; File logFileWrite = null; + private FilePadding filePadding = new FilePadding(); /** * constructor for FileTxnLog. Take the directory @@ -144,15 +133,6 @@ public FileTxnLog(File logDir) { this.logDir = logDir; } - /** - * method to allow setting preallocate size - * of log file to pad the file. - * @param size the size to set to in bytes - */ - public static void setPreallocSize(long size) { - preAllocSize = size; - } - /** * creates a checksum alogrithm to be used * @return the checksum used for this txnlog @@ -161,7 +141,6 @@ protected Checksum makeChecksumAlgorithm(){ return new Adler32(); } - /** * rollover the current log file to a new one. * @throws IOException @@ -213,18 +192,18 @@ public synchronized boolean append(TxnHeader hdr, Record txn) LOG.info("Creating new log file: " + Util.makeLogName(hdr.getZxid())); } - logFileWrite = new File(logDir, Util.makeLogName(hdr.getZxid())); - fos = new FileOutputStream(logFileWrite); - logStream=new BufferedOutputStream(fos); - oa = BinaryOutputArchive.getArchive(logStream); - FileHeader fhdr = new FileHeader(TXNLOG_MAGIC,VERSION, dbId); - fhdr.serialize(oa, "fileheader"); - // Make sure that the magic number is written before padding. - logStream.flush(); - currentSize = fos.getChannel().position(); - streamsToFlush.add(fos); - } - currentSize = padFile(fos.getChannel()); + logFileWrite = new File(logDir, Util.makeLogName(hdr.getZxid())); + fos = new FileOutputStream(logFileWrite); + logStream=new BufferedOutputStream(fos); + oa = BinaryOutputArchive.getArchive(logStream); + FileHeader fhdr = new FileHeader(TXNLOG_MAGIC,VERSION, dbId); + fhdr.serialize(oa, "fileheader"); + // Make sure that the magic number is written before padding. + logStream.flush(); + filePadding.setCurrentSize(fos.getChannel().position()); + streamsToFlush.add(fos); + } + filePadding.padFile(fos.getChannel()); byte[] buf = Util.marshallTxnEntry(hdr, txn); if (buf == null || buf.length == 0) { throw new IOException("Faulty serialization for header " + @@ -238,49 +217,6 @@ public synchronized boolean append(TxnHeader hdr, Record txn) return true; } - /** - * pad the current file to increase its size to the next multiple of preAllocSize greater than the current size and position - * @param fileChannel the fileChannel of the file to be padded - * @throws IOException - */ - private long padFile(FileChannel fileChannel) throws IOException { - long newFileSize = calculateFileSizeWithPadding(fileChannel.position(), currentSize, preAllocSize); - if (currentSize != newFileSize) { - fileChannel.write((ByteBuffer) fill.position(0), newFileSize - fill.remaining()); - currentSize = newFileSize; - } - return currentSize; - } - - /** - * Calculates a new file size with padding. We only return a new size if - * the current file position is sufficiently close (less than 4K) to end of - * file and preAllocSize is > 0. - * - * @param position the point in the file we have written to - * @param fileSize application keeps track of the current file size - * @param preAllocSize how many bytes to pad - * @return the new file size. It can be the same as fileSize if no - * padding was done. - * @throws IOException - */ - // VisibleForTesting - public static long calculateFileSizeWithPadding(long position, long fileSize, long preAllocSize) { - // If preAllocSize is positive and we are within 4KB of the known end of the file calculate a new file size - if (preAllocSize > 0 && position + 4096 >= fileSize) { - // If we have written more than we have previously preallocated we need to make sure the new - // file size is larger than what we already have - if (position > fileSize){ - fileSize = position + preAllocSize; - fileSize -= fileSize % preAllocSize; - } else { - fileSize += preAllocSize; - } - } - - return fileSize; - } - /** * Find the log file that starts at, or just before, the snapshot. Return * this and all subsequent logs. Results are ordered by zxid of file, diff --git a/src/java/main/org/apache/zookeeper/server/persistence/TxnLogToolkit.java b/src/java/main/org/apache/zookeeper/server/persistence/TxnLogToolkit.java new file mode 100644 index 00000000000..432cc8809c4 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/persistence/TxnLogToolkit.java @@ -0,0 +1,280 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.persistence; + +import org.apache.jute.BinaryInputArchive; +import org.apache.jute.BinaryOutputArchive; +import org.apache.jute.Record; +import org.apache.zookeeper.server.TraceFormatter; +import org.apache.zookeeper.server.util.SerializeUtils; +import org.apache.zookeeper.txn.TxnHeader; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.text.DateFormat; +import java.util.Date; +import java.util.Scanner; +import java.util.zip.Adler32; +import java.util.zip.Checksum; + +import static org.apache.zookeeper.server.persistence.FileTxnLog.TXNLOG_MAGIC; +import static org.apache.zookeeper.server.persistence.TxnLogToolkitCliParser.printHelpAndExit; + +public class TxnLogToolkit implements Closeable { + + static class TxnLogToolkitException extends Exception { + private static final long serialVersionUID = 1L; + private int exitCode; + + TxnLogToolkitException(int exitCode, String message, Object... params) { + super(String.format(message, params)); + this.exitCode = exitCode; + } + + int getExitCode() { + return exitCode; + } + } + + static class TxnLogToolkitParseException extends TxnLogToolkitException { + private static final long serialVersionUID = 1L; + + TxnLogToolkitParseException(int exitCode, String message, Object... params) { + super(exitCode, message, params); + } + } + + private File txnLogFile; + private boolean recoveryMode = false; + private boolean verbose = false; + private FileInputStream txnFis; + private BinaryInputArchive logStream; + + // Recovery mode + private int crcFixed = 0; + private FileOutputStream recoveryFos; + private BinaryOutputArchive recoveryOa; + private File recoveryLogFile; + private FilePadding filePadding = new FilePadding(); + private boolean force = false; + + /** + * @param args Command line arguments + */ + public static void main(String[] args) throws Exception { + final TxnLogToolkit lt = parseCommandLine(args); + try { + lt.dump(new InputStreamReader(System.in)); + lt.printStat(); + } catch (TxnLogToolkitParseException e) { + System.err.println(e.getMessage() + "\n"); + printHelpAndExit(e.getExitCode()); + } catch (TxnLogToolkitException e) { + System.err.println(e.getMessage()); + System.exit(e.getExitCode()); + } finally { + lt.close(); + } + } + + public TxnLogToolkit(boolean recoveryMode, boolean verbose, String txnLogFileName, boolean force) + throws FileNotFoundException, TxnLogToolkitException { + this.recoveryMode = recoveryMode; + this.verbose = verbose; + this.force = force; + txnLogFile = new File(txnLogFileName); + if (!txnLogFile.exists() || !txnLogFile.canRead()) { + throw new TxnLogToolkitException(1, "File doesn't exist or not readable: %s", txnLogFile); + } + if (recoveryMode) { + recoveryLogFile = new File(txnLogFile.toString() + ".fixed"); + if (recoveryLogFile.exists()) { + throw new TxnLogToolkitException(1, "Recovery file %s already exists or not writable", recoveryLogFile); + } + } + + openTxnLogFile(); + if (recoveryMode) { + openRecoveryFile(); + } + } + + public void dump(Reader input) throws Exception { + crcFixed = 0; + + FileHeader fhdr = new FileHeader(); + fhdr.deserialize(logStream, "fileheader"); + if (fhdr.getMagic() != TXNLOG_MAGIC) { + throw new TxnLogToolkitException(2, "Invalid magic number for %s", txnLogFile.getName()); + } + System.out.println("ZooKeeper Transactional Log File with dbid " + + fhdr.getDbid() + " txnlog format version " + + fhdr.getVersion()); + + if (recoveryMode) { + fhdr.serialize(recoveryOa, "fileheader"); + recoveryFos.flush(); + filePadding.setCurrentSize(recoveryFos.getChannel().position()); + } + + int count = 0; + while (true) { + long crcValue; + byte[] bytes; + try { + crcValue = logStream.readLong("crcvalue"); + bytes = logStream.readBuffer("txnEntry"); + } catch (EOFException e) { + System.out.println("EOF reached after " + count + " txns."); + return; + } + if (bytes.length == 0) { + // Since we preallocate, we define EOF to be an + // empty transaction + System.out.println("EOF reached after " + count + " txns."); + return; + } + Checksum crc = new Adler32(); + crc.update(bytes, 0, bytes.length); + if (crcValue != crc.getValue()) { + if (recoveryMode) { + if (!force) { + printTxn(bytes, "CRC ERROR"); + if (askForFix(input)) { + crcValue = crc.getValue(); + ++crcFixed; + } + } else { + crcValue = crc.getValue(); + printTxn(bytes, "CRC FIXED"); + ++crcFixed; + } + } else { + printTxn(bytes, "CRC ERROR"); + } + } + if (!recoveryMode || verbose) { + printTxn(bytes); + } + if (logStream.readByte("EOR") != 'B') { + throw new TxnLogToolkitException(1, "Last transaction was partial."); + } + if (recoveryMode) { + filePadding.padFile(recoveryFos.getChannel()); + recoveryOa.writeLong(crcValue, "crcvalue"); + recoveryOa.writeBuffer(bytes, "txnEntry"); + recoveryOa.writeByte((byte)'B', "EOR"); + } + count++; + } + } + + private boolean askForFix(Reader input) throws TxnLogToolkitException { + Scanner scanner = new Scanner(input); + try { + while (true) { + System.out.print("Would you like to fix it (Yes/No/Abort) ? "); + char answer = Character.toUpperCase(scanner.next().charAt(0)); + switch (answer) { + case 'Y': + return true; + case 'N': + return false; + case 'A': + throw new TxnLogToolkitException(0, "Recovery aborted."); + } + } + } finally { + scanner.close(); + } + } + + private void printTxn(byte[] bytes) throws IOException { + printTxn(bytes, ""); + } + + private void printTxn(byte[] bytes, String prefix) throws IOException { + TxnHeader hdr = new TxnHeader(); + Record txn = SerializeUtils.deserializeTxn(bytes, hdr); + String txns = String.format("%s session 0x%s cxid 0x%s zxid 0x%s %s %s", + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG).format(new Date(hdr.getTime())), + Long.toHexString(hdr.getClientId()), + Long.toHexString(hdr.getCxid()), + Long.toHexString(hdr.getZxid()), + TraceFormatter.op2String(hdr.getType()), + txn); + if (prefix != null && !"".equals(prefix.trim())) { + System.out.print(prefix + " - "); + } + if (txns.endsWith("\n")) { + System.out.print(txns); + } else { + System.out.println(txns); + } + } + + private void openTxnLogFile() throws FileNotFoundException { + txnFis = new FileInputStream(txnLogFile); + logStream = BinaryInputArchive.getArchive(txnFis); + } + + private void closeTxnLogFile() throws IOException { + if (txnFis != null) { + txnFis.close(); + } + } + + private void openRecoveryFile() throws FileNotFoundException { + recoveryFos = new FileOutputStream(recoveryLogFile); + recoveryOa = BinaryOutputArchive.getArchive(recoveryFos); + } + + private void closeRecoveryFile() throws IOException { + if (recoveryFos != null) { + recoveryFos.close(); + } + } + + private static TxnLogToolkit parseCommandLine(String[] args) throws TxnLogToolkitException, FileNotFoundException { + TxnLogToolkitCliParser parser = new TxnLogToolkitCliParser(); + parser.parse(args); + return new TxnLogToolkit(parser.isRecoveryMode(), parser.isVerbose(), parser.getTxnLogFileName(), parser.isForce()); + } + + private void printStat() { + if (recoveryMode) { + System.out.printf("Recovery file %s has been written with %d fixed CRC error(s)%n", recoveryLogFile, crcFixed); + } + } + + @Override + public void close() throws IOException { + if (recoveryMode) { + closeRecoveryFile(); + } + closeTxnLogFile(); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/persistence/TxnLogToolkitCliParser.java b/src/java/main/org/apache/zookeeper/server/persistence/TxnLogToolkitCliParser.java new file mode 100644 index 00000000000..094500a9f2d --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/persistence/TxnLogToolkitCliParser.java @@ -0,0 +1,101 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.persistence; + +class TxnLogToolkitCliParser { + private String txnLogFileName; + private boolean recoveryMode; + private boolean verbose; + private boolean force; + + String getTxnLogFileName() { + return txnLogFileName; + } + + boolean isRecoveryMode() { + return recoveryMode; + } + + boolean isVerbose() { + return verbose; + } + + boolean isForce() { + return force; + } + + void parse(String[] args) throws TxnLogToolkit.TxnLogToolkitParseException { + if (args == null) { + throw new TxnLogToolkit.TxnLogToolkitParseException(1, "No arguments given"); + } + txnLogFileName = null; + for (String arg : args) { + if (arg.startsWith("--")) { + String par = arg.substring(2); + if ("help".equalsIgnoreCase(par)) { + printHelpAndExit(0); + } else if ("recover".equalsIgnoreCase(par)) { + recoveryMode = true; + } else if ("verbose".equalsIgnoreCase(par)) { + verbose = true; + } else if ("dump".equalsIgnoreCase(par)) { + recoveryMode = false; + } else if ("yes".equalsIgnoreCase(par)) { + force = true; + } else { + throw new TxnLogToolkit.TxnLogToolkitParseException(1, "Invalid argument: %s", par); + } + } else if (arg.startsWith("-")) { + String par = arg.substring(1); + if ("h".equalsIgnoreCase(par)) { + printHelpAndExit(0); + } else if ("r".equalsIgnoreCase(par)) { + recoveryMode = true; + } else if ("v".equalsIgnoreCase(par)) { + verbose = true; + } else if ("d".equalsIgnoreCase(par)) { + recoveryMode = false; + } else if ("y".equalsIgnoreCase(par)) { + force = true; + } else { + throw new TxnLogToolkit.TxnLogToolkitParseException(1, "Invalid argument: %s", par); + } + } else { + if (txnLogFileName != null) { + throw new TxnLogToolkit.TxnLogToolkitParseException(1, "Invalid arguments: more than one TXN log file given"); + } + txnLogFileName = arg; + } + } + + if (txnLogFileName == null) { + throw new TxnLogToolkit.TxnLogToolkitParseException(1, "Invalid arguments: TXN log file name missing"); + } + } + + static void printHelpAndExit(int exitCode) { + System.out.println("usage: TxnLogToolkit [-dhrvy] txn_log_file_name\n"); + System.out.println(" -d,--dump Dump mode. Dump all entries of the log file. (this is the default)"); + System.out.println(" -h,--help Print help message"); + System.out.println(" -r,--recover Recovery mode. Re-calculate CRC for broken entries."); + System.out.println(" -v,--verbose Be verbose in recovery mode: print all entries, not just fixed ones."); + System.out.println(" -y,--yes Non-interactive mode: repair all CRC errors without asking"); + System.exit(exitCode); + } +} diff --git a/src/java/test/data/invalidsnap/version-2/log.42 b/src/java/test/data/invalidsnap/version-2/log.42 new file mode 100644 index 00000000000..5385be516e2 Binary files /dev/null and b/src/java/test/data/invalidsnap/version-2/log.42 differ diff --git a/src/java/test/org/apache/zookeeper/server/persistence/FileTxnLogTest.java b/src/java/test/org/apache/zookeeper/server/persistence/FileTxnLogTest.java index 5f54d0e388f..97cbc372af4 100644 --- a/src/java/test/org/apache/zookeeper/server/persistence/FileTxnLogTest.java +++ b/src/java/test/org/apache/zookeeper/server/persistence/FileTxnLogTest.java @@ -39,27 +39,27 @@ public class FileTxnLogTest extends ZKTestCase { @Test public void testInvalidPreallocSize() { Assert.assertEquals("file should not be padded", - 10 * KB, FileTxnLog.calculateFileSizeWithPadding(7 * KB, 10 * KB, 0)); + 10 * KB, FilePadding.calculateFileSizeWithPadding(7 * KB, 10 * KB, 0)); Assert.assertEquals("file should not be padded", - 10 * KB, FileTxnLog.calculateFileSizeWithPadding(7 * KB, 10 * KB, -1)); + 10 * KB, FilePadding.calculateFileSizeWithPadding(7 * KB, 10 * KB, -1)); } @Test public void testCalculateFileSizeWithPaddingWhenNotToCurrentSize() { Assert.assertEquals("file should not be padded", - 10 * KB, FileTxnLog.calculateFileSizeWithPadding(5 * KB, 10 * KB, 10 * KB)); + 10 * KB, FilePadding.calculateFileSizeWithPadding(5 * KB, 10 * KB, 10 * KB)); } @Test public void testCalculateFileSizeWithPaddingWhenCloseToCurrentSize() { Assert.assertEquals("file should be padded an additional 10 KB", - 20 * KB, FileTxnLog.calculateFileSizeWithPadding(7 * KB, 10 * KB, 10 * KB)); + 20 * KB, FilePadding.calculateFileSizeWithPadding(7 * KB, 10 * KB, 10 * KB)); } @Test public void testFileSizeGreaterThanPosition() { Assert.assertEquals("file should be padded to 40 KB", - 40 * KB, FileTxnLog.calculateFileSizeWithPadding(31 * KB, 10 * KB, 10 * KB)); + 40 * KB, FilePadding.calculateFileSizeWithPadding(31 * KB, 10 * KB, 10 * KB)); } @Test @@ -69,7 +69,7 @@ public void testPreAllocSizeSmallerThanTxnData() throws IOException { // Set a small preAllocSize (.5 MB) final int preAllocSize = 500 * KB; - fileTxnLog.setPreallocSize(preAllocSize); + FilePadding.setPreallocSize(preAllocSize); // Create dummy txn larger than preAllocSize // Since the file padding inserts a 0, we will fill the data with 0xff to ensure we corrupt the data if we put the 0 in the data diff --git a/src/java/test/org/apache/zookeeper/server/persistence/TxnLogToolkitCliParserTest.java b/src/java/test/org/apache/zookeeper/server/persistence/TxnLogToolkitCliParserTest.java new file mode 100644 index 00000000000..ee4dc061c71 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/persistence/TxnLogToolkitCliParserTest.java @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.persistence; + +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class TxnLogToolkitCliParserTest { + + private TxnLogToolkitCliParser parser; + + @Before + public void setUp() { + parser = new TxnLogToolkitCliParser(); + } + + @Test(expected = TxnLogToolkit.TxnLogToolkitParseException.class) + public void testParseWithNoArguments() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(null); + } + + @Test(expected = TxnLogToolkit.TxnLogToolkitParseException.class) + public void testParseWithEmptyArgs() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[0]); + } + + @Test(expected = TxnLogToolkit.TxnLogToolkitParseException.class) + public void testParseWith2Filenames() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[] { "file1.log", "file2.log "}); + } + + @Test(expected = TxnLogToolkit.TxnLogToolkitParseException.class) + public void testParseWithInvalidShortSwitch() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[] { "-v", "-i", "txnlog.txt" }); + } + + @Test(expected = TxnLogToolkit.TxnLogToolkitParseException.class) + public void testParseWithInvalidLongSwitch() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[] { "-v", "--invalid", "txnlog.txt" }); + } + + @Test + public void testParseRecoveryModeSwitchShort() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[] { "-r", "txnlog.txt"}); + assertThat("Recovery short switch should turn on recovery mode", parser.isRecoveryMode(), is(true)); + } + + @Test + public void testParseRecoveryModeSwitchLong() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[] { "--recover", "txnlog.txt"}); + assertThat("Recovery long switch should turn on recovery mode", parser.isRecoveryMode(), is(true)); + } + + @Test + public void testParseVerboseModeSwitchShort() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[] { "-v", "txnlog.txt"}); + assertThat("Verbose short switch should turn on verbose mode", parser.isVerbose(), is(true)); + } + + @Test + public void testParseVerboseModeSwitchLong() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[] { "--verbose", "txnlog.txt"}); + assertThat("Verbose long switch should turn on verbose mode", parser.isVerbose(), is(true)); + } + + @Test + public void testParseDumpModeSwitchShort() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[] { "-r", "txnlog.txt"}); // turn on + parser.parse(new String[] { "-d", "txnlog.txt"}); // turn off + assertThat("Dump short switch should turn off recover mode", parser.isRecoveryMode(), is(false)); + } + + @Test + public void testParseDumpModeSwitchLong() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[] { "-r", "txnlog.txt"}); // turn on + parser.parse(new String[] { "--dump", "txnlog.txt"}); // turn off + assertThat("Dump long switch should turn off recovery mode", parser.isRecoveryMode(), is(false)); + } + + @Test + public void testParseForceModeSwitchShort() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[] { "-y", "txnlog.txt"}); + assertThat("Force short switch should turn on force mode", parser.isForce(), is(true)); + } + + @Test + public void testParseForceModeSwitchLong() throws TxnLogToolkit.TxnLogToolkitParseException { + parser.parse(new String[] { "--yes", "txnlog.txt"}); + assertThat("Force long switch should turn on force mode", parser.isForce(), is(true)); + } +} diff --git a/src/java/test/org/apache/zookeeper/server/persistence/TxnLogToolkitTest.java b/src/java/test/org/apache/zookeeper/server/persistence/TxnLogToolkitTest.java new file mode 100644 index 00000000000..62557d14676 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/persistence/TxnLogToolkitTest.java @@ -0,0 +1,155 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.persistence; + +import org.apache.commons.io.FileUtils; +import org.apache.zookeeper.test.ClientBase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.io.StringReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.matchers.JUnitMatchers.containsString; + + +public class TxnLogToolkitTest { + private static final File testData = new File( + System.getProperty("test.data.dir", "build/test/data")); + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + private File mySnapDir; + + @Before + public void setUp() throws IOException { + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(errContent)); + File snapDir = new File(testData, "invalidsnap"); + mySnapDir = ClientBase.createTmpDir(); + FileUtils.copyDirectory(snapDir, mySnapDir); + } + + @After + public void tearDown() throws IOException { + System.setOut(System.out); + System.setErr(System.err); + mySnapDir.setWritable(true); + FileUtils.deleteDirectory(mySnapDir); + } + + @Test + public void testDumpMode() throws Exception { + // Arrange + File logfile = new File(new File(mySnapDir, "version-2"), "log.274"); + TxnLogToolkit lt = new TxnLogToolkit(false, false, logfile.toString(), true); + + // Act + lt.dump(null); + + // Assert + // no exception thrown + } + + @Test(expected = TxnLogToolkit.TxnLogToolkitException.class) + public void testInitMissingFile() throws FileNotFoundException, TxnLogToolkit.TxnLogToolkitException { + // Arrange & Act + File logfile = new File("this_file_should_not_exists"); + TxnLogToolkit lt = new TxnLogToolkit(false, false, logfile.toString(), true); + } + + @Test(expected = TxnLogToolkit.TxnLogToolkitException.class) + public void testInitWithRecoveryFileExists() throws IOException, TxnLogToolkit.TxnLogToolkitException { + // Arrange & Act + File logfile = new File(new File(mySnapDir, "version-2"), "log.274"); + File recoveryFile = new File(new File(mySnapDir, "version-2"), "log.274.fixed"); + recoveryFile.createNewFile(); + TxnLogToolkit lt = new TxnLogToolkit(true, false, logfile.toString(), true); + } + + @Test + public void testDumpWithCrcError() throws Exception { + // Arrange + File logfile = new File(new File(mySnapDir, "version-2"), "log.42"); + TxnLogToolkit lt = new TxnLogToolkit(false, false, logfile.toString(), true); + + // Act + lt.dump(null); + + // Assert + String output = outContent.toString(); + Pattern p = Pattern.compile("^CRC ERROR.*session 0x8061fac5ddeb0000 cxid 0x0 zxid 0x8800000002 createSession 30000$", Pattern.MULTILINE); + Matcher m = p.matcher(output); + assertTrue("Output doesn't indicate CRC error for the broken session id: " + output, m.find()); + } + + @Test + public void testRecoveryFixBrokenFile() throws Exception { + // Arrange + File logfile = new File(new File(mySnapDir, "version-2"), "log.42"); + TxnLogToolkit lt = new TxnLogToolkit(true, false, logfile.toString(), true); + + // Act + lt.dump(null); + + // Assert + String output = outContent.toString(); + assertThat(output, containsString("CRC FIXED")); + + // Should be able to dump the recovered logfile with no CRC error + outContent.reset(); + logfile = new File(new File(mySnapDir, "version-2"), "log.42.fixed"); + lt = new TxnLogToolkit(false, false, logfile.toString(), true); + lt.dump(null); + output = outContent.toString(); + assertThat(output, not(containsString("CRC ERROR"))); + } + + @Test + public void testRecoveryInteractiveMode() throws Exception { + // Arrange + File logfile = new File(new File(mySnapDir, "version-2"), "log.42"); + TxnLogToolkit lt = new TxnLogToolkit(true, false, logfile.toString(), false); + + // Act + lt.dump(new StringReader("y\n")); + + // Assert + String output = outContent.toString(); + assertThat(output, containsString("CRC ERROR")); + + // Should be able to dump the recovered logfile with no CRC error + outContent.reset(); + logfile = new File(new File(mySnapDir, "version-2"), "log.42.fixed"); + lt = new TxnLogToolkit(false, false, logfile.toString(), true); + lt.dump(null); + output = outContent.toString(); + assertThat(output, not(containsString("CRC ERROR"))); + } +} diff --git a/src/java/test/org/apache/zookeeper/test/ClientBase.java b/src/java/test/org/apache/zookeeper/test/ClientBase.java index 74d0eed1107..7de4f3c3267 100644 --- a/src/java/test/org/apache/zookeeper/test/ClientBase.java +++ b/src/java/test/org/apache/zookeeper/test/ClientBase.java @@ -56,6 +56,7 @@ import org.apache.zookeeper.server.ServerCnxnFactoryAccessor; import org.apache.zookeeper.server.ZKDatabase; import org.apache.zookeeper.server.ZooKeeperServer; +import org.apache.zookeeper.server.persistence.FilePadding; import org.apache.zookeeper.server.persistence.FileTxnLog; import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.server.util.OSMXBean; @@ -467,7 +468,7 @@ public static void setupTestEnv() { // resulting in test Assert.failure (client timeout on first session). // set env and directly in order to handle static init/gc issues System.setProperty("zookeeper.preAllocSize", "100"); - FileTxnLog.setPreallocSize(100 * 1024); + FilePadding.setPreallocSize(100 * 1024); } protected void setUpAll() throws Exception {