| /* |
| * 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 names. (MINUTELY, HOURLY,\n" + |
| " 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))); |
| } |
| } |
| } |