/*
* 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.cassandra.db;
import java.io.*;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import org.junit.Test;
import org.apache.cassandra.CleanupHelper;
import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.db.commitlog.CommitLogHeader;
import org.apache.cassandra.db.filter.QueryPath;
import org.apache.cassandra.utils.Pair;
public class CommitLogTest extends CleanupHelper
{
@Test
public void testRecoveryWithEmptyHeader() throws Exception
{
testRecovery(new byte[0], new byte[10]);
}
@Test
public void testRecoveryWithShortHeader() throws Exception
{
testRecovery(new byte[2], new byte[10]);
}
@Test
public void testRecoveryWithGarbageHeader() throws Exception
{
byte[] garbage = new byte[100];
(new java.util.Random()).nextBytes(garbage);
testRecovery(garbage, garbage);
}
@Test
public void testRecoveryWithEmptyLog() throws Exception
{
CommitLog.recover(new File[] {tmpFiles().right});
}
@Test
public void testRecoveryWithShortLog() throws Exception
{
// force EOF while reading log
testRecoveryWithBadSizeArgument(100, 10);
}
@Test
public void testRecoveryWithShortSize() throws Exception
{
testRecovery(new byte[0], new byte[2]);
}
@Test
public void testRecoveryWithShortCheckSum() throws Exception
{
testRecovery(new byte[0], new byte[6]);
}
@Test
public void testRecoveryWithGarbageLog() throws Exception
{
byte[] garbage = new byte[100];
(new java.util.Random()).nextBytes(garbage);
testRecovery(new byte[0], garbage);
}
@Test
public void testRecoveryWithBadSizeChecksum() throws Exception
{
Checksum checksum = new CRC32();
checksum.update(100);
testRecoveryWithBadSizeArgument(100, 100, ~checksum.getValue());
}
@Test
public void testRecoveryWithZeroSegmentSizeArgument() throws Exception
{
// many different combinations of 4 bytes (garbage) will be read as zero by readInt()
testRecoveryWithBadSizeArgument(0, 10); // zero size, but no EOF
}
@Test
public void testRecoveryWithNegativeSizeArgument() throws Exception
{
// garbage from a partial/bad flush could be read as a negative size even if there is no EOF
testRecoveryWithBadSizeArgument(-10, 10); // negative size, but no EOF
}
@Test
public void testRecoveryWithHeaderPositionGreaterThanLogLength() throws Exception
{
// Note: this can actually happen (in periodic mode) when data is flushed
// before it had time to hit the commitlog (since the header is flushed by the system)
// see https://issues.apache.org/jira/browse/CASSANDRA-2285
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(out);
Checksum checksum = new CRC32();
// write the first checksum after the fixed-size part, so we won't read garbage lastFlushedAt data.
dos.writeInt(1);
checksum.update(1);
dos.writeLong(checksum.getValue());
dos.writeInt(0);
checksum.update(0);
dos.writeInt(200);
checksum.update(200);
dos.writeLong(checksum.getValue());
dos.close();
testRecovery(out.toByteArray(), new byte[0]);
}
protected void testRecoveryWithBadSizeArgument(int size, int dataSize) throws Exception
{
Checksum checksum = new CRC32();
checksum.update(size);
testRecoveryWithBadSizeArgument(size, dataSize, checksum.getValue());
}
protected void testRecoveryWithBadSizeArgument(int size, int dataSize, long checksum) throws Exception
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(out);
dout.writeInt(size);
dout.writeLong(checksum);
dout.write(new byte[dataSize]);
dout.close();
testRecovery(new byte[0], out.toByteArray());
}
protected Pair<File, File> tmpFiles() throws IOException
{
File logFile = File.createTempFile("testRecoveryWithPartiallyWrittenHeaderTestFile", null);
File headerFile = new File(CommitLogHeader.getHeaderPathFromSegmentPath(logFile.getAbsolutePath()));
logFile.deleteOnExit();
headerFile.deleteOnExit();
assert logFile.length() == 0;
assert headerFile.length() == 0;
return new Pair<File, File>(headerFile, logFile);
}
protected void testRecovery(byte[] headerData, byte[] logData) throws Exception
{
Pair<File, File> tmpFiles = tmpFiles();
File logFile = tmpFiles.right;
File headerFile = tmpFiles.left;
OutputStream lout = new FileOutputStream(logFile);
OutputStream hout = new FileOutputStream(headerFile);
lout.write(logData);
hout.write(headerData);
//statics make it annoying to test things correctly
CommitLog.recover(new File[] {logFile}); //CASSANDRA-1119 / CASSANDRA-1179 throw on failure*/
}
}