mirror of https://github.com/apache/cassandra
309 lines
12 KiB
Java
309 lines
12 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.tools;
|
|
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.Lists;
|
|
|
|
import org.apache.cassandra.io.util.File;
|
|
import org.apache.commons.io.FileUtils;
|
|
|
|
import org.junit.After;
|
|
import org.junit.Assert;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
|
|
import net.openhft.chronicle.core.io.IORuntimeException;
|
|
import net.openhft.chronicle.queue.ChronicleQueue;
|
|
import net.openhft.chronicle.queue.impl.single.SingleChronicleQueueBuilder;
|
|
import net.openhft.chronicle.queue.ExcerptAppender;
|
|
import net.openhft.chronicle.queue.RollCycles;
|
|
import net.openhft.chronicle.wire.WireOut;
|
|
import org.apache.cassandra.audit.BinAuditLogger;
|
|
import org.apache.cassandra.tools.ToolRunner.ObservableTool;
|
|
import org.apache.cassandra.tools.ToolRunner.ToolResult;
|
|
import org.assertj.core.api.Assertions;
|
|
import org.hamcrest.CoreMatchers;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertThat;
|
|
import static org.junit.Assert.assertTrue;
|
|
|
|
public class AuditLogViewerTest
|
|
{
|
|
private Path path;
|
|
private final String toolPath = "tools/bin/auditlogviewer";
|
|
|
|
@Before
|
|
public void setUp() throws IOException
|
|
{
|
|
path = Files.createTempDirectory("foo");
|
|
}
|
|
|
|
@After
|
|
public void tearDown() throws IOException
|
|
{
|
|
if (new File(path).exists() && new File(path).isDirectory())
|
|
{
|
|
//Deletes directory and all of it's contents
|
|
FileUtils.deleteDirectory(new File(path).toJavaIOFile());
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testNoArgs()
|
|
{
|
|
ToolResult tool = ToolRunner.invoke(toolPath);
|
|
assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:"));
|
|
assertThat(tool.getCleanedStderr(), CoreMatchers.containsStringIgnoringCase("Audit log files directory path is a required argument."));
|
|
assertEquals(1, tool.getExitCode());
|
|
}
|
|
|
|
@Test
|
|
public void testMaybeChangeDocs()
|
|
{
|
|
// If you added, modified options or help, please update docs if necessary
|
|
ToolResult tool = ToolRunner.invoke(toolPath, "-h");
|
|
String help = "usage: auditlogviewer <path1> [<path2>...<pathN>] [options]\n" +
|
|
"--\n" +
|
|
"View the audit log contents in human readable format\n" +
|
|
"--\n" +
|
|
"Options are:\n" +
|
|
" -f,--follow Upon reacahing the end of the log continue\n" +
|
|
" indefinitely waiting for more records\n" +
|
|
" -h,--help display this help message\n" +
|
|
" -i,--ignore Silently ignore unsupported records\n" +
|
|
" -r,--roll_cycle <arg> How often to roll the log file was rolled. May be\n" +
|
|
" necessary for Chronicle to correctly parse file\n" +
|
|
" names. (MINUTELY, HOURLY, DAILY). Default HOURLY.\n";
|
|
Assertions.assertThat(tool.getStdout()).isEqualTo(help);
|
|
}
|
|
|
|
@Test
|
|
public void testHelpArg()
|
|
{
|
|
Arrays.asList("-h", "--help").forEach(arg -> {
|
|
ToolResult tool = ToolRunner.invoke(toolPath, arg);
|
|
assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:"));
|
|
assertTrue(tool.getCleanedStderr(),tool.getCleanedStderr().isEmpty());
|
|
tool.assertOnExitCode();
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void testIgnoreArg()
|
|
{
|
|
Arrays.asList("-i", "--ignore").forEach(arg -> {
|
|
ToolResult tool = ToolRunner.invoke(toolPath, path.toAbsolutePath().toString(), arg);
|
|
assertTrue(tool.getStdout(), tool.getStdout().isEmpty());
|
|
// @IgnoreAssert see CASSANDRA-16021
|
|
// assertTrue(tool.getCleanedStderr(),
|
|
// tool.getCleanedStderr().isEmpty() // j8 is fine
|
|
// || tool.getCleanedStderr().startsWith("WARNING: An illegal reflective access operation has occurred")); //j11 throws an error
|
|
tool.assertOnExitCode();
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void testFollowNRollArgs()
|
|
{
|
|
Lists.cartesianProduct(Arrays.asList("-f", "--follow"), Arrays.asList("-r", "--roll_cycle")).forEach(arg -> {
|
|
try (ObservableTool tool = ToolRunner.invokeAsync(toolPath,
|
|
path.toAbsolutePath().toString(),
|
|
arg.get(0),
|
|
arg.get(1),
|
|
"TEST_SECONDLY");)
|
|
{
|
|
// Tool is running in the background 'following' so wait and then we have to kill it
|
|
try
|
|
{
|
|
Thread.sleep(3000);
|
|
}
|
|
catch(InterruptedException e)
|
|
{
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
assertTrue(tool.getPartialStdout(), tool.getPartialStdout().isEmpty());
|
|
// @IgnoreAssert see CASSANDRA-16021
|
|
// assertTrue(tool.getCleanedStderr(),
|
|
// tool.getCleanedStderr().isEmpty() // j8 is fine
|
|
// || tool.getCleanedStderr().startsWith("WARNING: An illegal reflective access operation has occurred")); //j11 throws an error
|
|
}
|
|
});
|
|
}
|
|
|
|
@Test
|
|
public void testDisplayRecord()
|
|
{
|
|
List<String> records = new ArrayList<>();
|
|
records.add("Test foo bar 1");
|
|
records.add("Test foo bar 2");
|
|
|
|
try (ChronicleQueue queue = SingleChronicleQueueBuilder.single(path.toFile()).rollCycle(RollCycles.TEST_SECONDLY).build())
|
|
{
|
|
ExcerptAppender appender = queue.acquireAppender();
|
|
|
|
//Write bunch of records
|
|
records.forEach(s -> appender.writeDocument(new BinAuditLogger.Message(s)));
|
|
|
|
//Read those written records
|
|
List<String> actualRecords = new ArrayList<>();
|
|
AuditLogViewer.dump(ImmutableList.of(path.toString()), RollCycles.TEST_SECONDLY.toString(), false, false, actualRecords::add);
|
|
|
|
assertRecordsMatch(records, actualRecords);
|
|
}
|
|
}
|
|
|
|
@Test (expected = IORuntimeException.class)
|
|
public void testRejectFutureVersionRecord()
|
|
{
|
|
try (ChronicleQueue queue = SingleChronicleQueueBuilder.single(path.toFile()).rollCycle(RollCycles.TEST_SECONDLY).build())
|
|
{
|
|
ExcerptAppender appender = queue.acquireAppender();
|
|
appender.writeDocument(createFutureRecord());
|
|
|
|
AuditLogViewer.dump(ImmutableList.of(path.toString()), RollCycles.TEST_SECONDLY.toString(), false, false, dummy -> {});
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
assertTrue(e.getMessage().contains("Unsupported record version"));
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testIgnoreFutureVersionRecord()
|
|
{
|
|
List<String> records = new ArrayList<>();
|
|
records.add("Test foo bar 1");
|
|
records.add("Test foo bar 2");
|
|
|
|
try (ChronicleQueue queue = SingleChronicleQueueBuilder.single(path.toFile()).rollCycle(RollCycles.TEST_SECONDLY).build())
|
|
{
|
|
ExcerptAppender appender = queue.acquireAppender();
|
|
|
|
//Write future record
|
|
appender.writeDocument(createFutureRecord());
|
|
|
|
//Write bunch of current records
|
|
records.forEach(s -> appender.writeDocument(new BinAuditLogger.Message(s)));
|
|
|
|
//Read those written records
|
|
List<String> actualRecords = new ArrayList<>();
|
|
AuditLogViewer.dump(ImmutableList.of(path.toString()), RollCycles.TEST_SECONDLY.toString(), false, true, actualRecords::add);
|
|
|
|
// Assert all current records are present
|
|
assertRecordsMatch(records, actualRecords);
|
|
}
|
|
}
|
|
|
|
@Test (expected = IORuntimeException.class)
|
|
public void testRejectUnknownTypeRecord()
|
|
{
|
|
try (ChronicleQueue queue = SingleChronicleQueueBuilder.single(path.toFile()).rollCycle(RollCycles.TEST_SECONDLY).build())
|
|
{
|
|
ExcerptAppender appender = queue.acquireAppender();
|
|
appender.writeDocument(createUnknownTypeRecord());
|
|
|
|
AuditLogViewer.dump(ImmutableList.of(path.toString()), RollCycles.TEST_SECONDLY.toString(), false, false, dummy -> {});
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
assertTrue(e.getMessage().contains("Unsupported record type field"));
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testIgnoreUnknownTypeRecord()
|
|
{
|
|
List<String> records = new ArrayList<>();
|
|
records.add("Test foo bar 1");
|
|
records.add("Test foo bar 2");
|
|
|
|
try (ChronicleQueue queue = SingleChronicleQueueBuilder.single(path.toFile()).rollCycle(RollCycles.TEST_SECONDLY).build())
|
|
{
|
|
ExcerptAppender appender = queue.acquireAppender();
|
|
|
|
//Write unrecognized type record
|
|
appender.writeDocument(createUnknownTypeRecord());
|
|
|
|
//Write bunch of supported records
|
|
records.forEach(s -> appender.writeDocument(new BinAuditLogger.Message(s)));
|
|
|
|
//Read those written records
|
|
List<String> actualRecords = new ArrayList<>();
|
|
AuditLogViewer.dump(ImmutableList.of(path.toString()), RollCycles.TEST_SECONDLY.toString(), false, true, actualRecords::add);
|
|
|
|
// Assert all supported records are present
|
|
assertRecordsMatch(records, actualRecords);
|
|
}
|
|
}
|
|
|
|
private BinAuditLogger.Message createFutureRecord()
|
|
{
|
|
return new BinAuditLogger.Message("dummy message") {
|
|
protected long version()
|
|
{
|
|
return 999;
|
|
}
|
|
|
|
@Override
|
|
public void writeMarshallablePayload(WireOut wire)
|
|
{
|
|
super.writeMarshallablePayload(wire);
|
|
wire.write("future-field").text("future_value");
|
|
}
|
|
};
|
|
}
|
|
|
|
private BinAuditLogger.Message createUnknownTypeRecord()
|
|
{
|
|
return new BinAuditLogger.Message("dummy message") {
|
|
protected String type()
|
|
{
|
|
return "unknown-type";
|
|
}
|
|
|
|
@Override
|
|
public void writeMarshallablePayload(WireOut wire)
|
|
{
|
|
super.writeMarshallablePayload(wire);
|
|
wire.write("unknown-field").text("unknown_value");
|
|
}
|
|
};
|
|
}
|
|
|
|
private void assertRecordsMatch(List<String> records, List<String> actualRecords)
|
|
{
|
|
Assert.assertEquals(records.size(), actualRecords.size());
|
|
for (int i = 0; i < records.size(); i++)
|
|
{
|
|
Assert.assertTrue(actualRecords.get(i).contains(records.get(i)));
|
|
}
|
|
}
|
|
}
|