mirror of https://github.com/apache/cassandra
207 lines
7.5 KiB
Java
207 lines
7.5 KiB
Java
/*
|
|
* 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.nio.ByteBuffer;
|
|
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.filter.QueryPath;
|
|
|
|
import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
|
|
|
|
public class CommitLogTest extends CleanupHelper
|
|
{
|
|
@Test
|
|
public void testRecoveryWithEmptyLog() throws Exception
|
|
{
|
|
CommitLog.instance.recover(new File[]{ tmpFile() });
|
|
}
|
|
|
|
@Test
|
|
public void testRecoveryWithShortLog() throws Exception
|
|
{
|
|
// force EOF while reading log
|
|
testRecoveryWithBadSizeArgument(100, 10);
|
|
}
|
|
|
|
@Test
|
|
public void testRecoveryWithShortSize() throws Exception
|
|
{
|
|
testRecovery(new byte[2]);
|
|
}
|
|
|
|
@Test
|
|
public void testRecoveryWithShortCheckSum() throws Exception
|
|
{
|
|
testRecovery(new byte[6]);
|
|
}
|
|
|
|
@Test
|
|
public void testRecoveryWithGarbageLog() throws Exception
|
|
{
|
|
byte[] garbage = new byte[100];
|
|
(new java.util.Random()).nextBytes(garbage);
|
|
testRecovery(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 testDontDeleteIfDirty() throws Exception
|
|
{
|
|
CommitLog.instance.resetUnsafe();
|
|
// Roughly 32 MB mutation
|
|
RowMutation rm = new RowMutation("Keyspace1", bytes("k"));
|
|
rm.add(new QueryPath("Standard1", null, bytes("c1")), ByteBuffer.allocate(32 * 1024 * 1024), 0);
|
|
|
|
// Adding it 5 times
|
|
CommitLog.instance.add(rm);
|
|
CommitLog.instance.add(rm);
|
|
CommitLog.instance.add(rm);
|
|
CommitLog.instance.add(rm);
|
|
CommitLog.instance.add(rm);
|
|
|
|
// Adding new mutation on another CF
|
|
RowMutation rm2 = new RowMutation("Keyspace1", bytes("k"));
|
|
rm2.add(new QueryPath("Standard2", null, bytes("c1")), ByteBuffer.allocate(4), 0);
|
|
CommitLog.instance.add(rm2);
|
|
|
|
assert CommitLog.instance.activeSegments() == 2 : "Expecting 2 segments, got " + CommitLog.instance.activeSegments();
|
|
|
|
int cfid2 = rm2.getColumnFamilyIds().iterator().next();
|
|
CommitLog.instance.discardCompletedSegments(cfid2, CommitLog.instance.getContext());
|
|
|
|
// Assert we still have both our segment
|
|
assert CommitLog.instance.activeSegments() == 2 : "Expecting 2 segments, got " + CommitLog.instance.activeSegments();
|
|
}
|
|
|
|
@Test
|
|
public void testDeleteIfNotDirty() throws Exception
|
|
{
|
|
CommitLog.instance.resetUnsafe();
|
|
// Roughly 32 MB mutation
|
|
RowMutation rm = new RowMutation("Keyspace1", bytes("k"));
|
|
rm.add(new QueryPath("Standard1", null, bytes("c1")), ByteBuffer.allocate(32 * 1024 * 1024), 0);
|
|
|
|
// Adding it twice (won't change segment)
|
|
CommitLog.instance.add(rm);
|
|
CommitLog.instance.add(rm);
|
|
|
|
assert CommitLog.instance.activeSegments() == 1 : "Expecting 1 segment, got " + CommitLog.instance.activeSegments();
|
|
|
|
// "Flush": this won't delete anything
|
|
int cfid1 = rm.getColumnFamilyIds().iterator().next();
|
|
CommitLog.instance.discardCompletedSegments(cfid1, CommitLog.instance.getContext());
|
|
|
|
assert CommitLog.instance.activeSegments() == 1 : "Expecting 1 segment, got " + CommitLog.instance.activeSegments();
|
|
|
|
// Adding new mutation on another CF, large enough (including CL entry overhead) that a new segment is created
|
|
RowMutation rm2 = new RowMutation("Keyspace1", bytes("k"));
|
|
rm2.add(new QueryPath("Standard2", null, bytes("c1")), ByteBuffer.allocate(64 * 1024 * 1024), 0);
|
|
CommitLog.instance.add(rm2);
|
|
// also forces a new segment, since each entry-with-overhead is just over half the CL size
|
|
CommitLog.instance.add(rm2);
|
|
|
|
assert CommitLog.instance.activeSegments() == 3 : "Expecting 3 segments, got " + CommitLog.instance.activeSegments();
|
|
|
|
|
|
// "Flush" second cf: The first segment should be deleted since we
|
|
// didn't write anything on cf1 since last flush (and we flush cf2)
|
|
|
|
int cfid2 = rm2.getColumnFamilyIds().iterator().next();
|
|
CommitLog.instance.discardCompletedSegments(cfid2, CommitLog.instance.getContext());
|
|
|
|
// Assert we still have both our segment
|
|
assert CommitLog.instance.activeSegments() == 1 : "Expecting 1 segment, got " + CommitLog.instance.activeSegments();
|
|
}
|
|
|
|
// CASSANDRA-3615
|
|
@Test
|
|
public void testExceedSegmentSizeWithOverhead() throws Exception
|
|
{
|
|
CommitLog.instance.resetUnsafe();
|
|
|
|
RowMutation rm = new RowMutation("Keyspace1", bytes("k"));
|
|
rm.add(new QueryPath("Standard1", null, bytes("c1")), ByteBuffer.allocate((128 * 1024 * 1024) - 83), 0);
|
|
CommitLog.instance.add(rm);
|
|
}
|
|
|
|
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(out.toByteArray());
|
|
}
|
|
|
|
protected File tmpFile() throws IOException
|
|
{
|
|
File logFile = File.createTempFile("testRecoveryWithPartiallyWrittenHeaderTestFile", null);
|
|
logFile.deleteOnExit();
|
|
assert logFile.length() == 0;
|
|
return logFile;
|
|
}
|
|
|
|
protected void testRecovery(byte[] logData) throws Exception
|
|
{
|
|
File logFile = tmpFile();
|
|
OutputStream lout = new FileOutputStream(logFile);
|
|
lout.write(logData);
|
|
//statics make it annoying to test things correctly
|
|
CommitLog.instance.recover(new File[]{ logFile }); //CASSANDRA-1119 / CASSANDRA-1179 throw on failure*/
|
|
}
|
|
}
|