This is an automated email from the ASF dual-hosted git repository.

abhi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/master by this push:
     new 87666d306 RANGER-5102: Add config param for writing audits to HDFS in 
append mode (#513)
87666d306 is described below

commit 87666d306c1972a35117085f9966aa9263ad67cb
Author: Abhishek Kumar <[email protected]>
AuthorDate: Mon Feb 24 15:14:57 2025 -0800

    RANGER-5102: Add config param for writing audits to HDFS in append mode 
(#513)
    
    - added new config param to enable/disable append mode
    - added unit tests
---
 .../audit/utils/AbstractRangerAuditWriter.java     |  39 +++----
 .../ranger/audit/utils/RangerJSONAuditWriter.java  |   5 -
 .../audit/utils/RangerJSONAuditWriterTest.java     | 117 ++++++++++++++++-----
 3 files changed, 109 insertions(+), 52 deletions(-)

diff --git 
a/agents-audit/src/main/java/org/apache/ranger/audit/utils/AbstractRangerAuditWriter.java
 
b/agents-audit/src/main/java/org/apache/ranger/audit/utils/AbstractRangerAuditWriter.java
index c3352ec16..1337455d6 100644
--- 
a/agents-audit/src/main/java/org/apache/ranger/audit/utils/AbstractRangerAuditWriter.java
+++ 
b/agents-audit/src/main/java/org/apache/ranger/audit/utils/AbstractRangerAuditWriter.java
@@ -45,12 +45,13 @@
 public abstract class AbstractRangerAuditWriter implements RangerAuditWriter {
     private static final Logger logger = 
LoggerFactory.getLogger(AbstractRangerAuditWriter.class);
 
-    public static final String  PROP_FILESYSTEM_DIR              = "dir";
-    public static final String  PROP_FILESYSTEM_SUBDIR           = "subdir";
-    public static final String  PROP_FILESYSTEM_FILE_NAME_FORMAT = 
"filename.format";
-    public static final String  PROP_FILESYSTEM_FILE_ROLLOVER    = 
"file.rollover.sec";
-    public static final String  PROP_FILESYSTEM_ROLLOVER_PERIOD  = 
"file.rollover.period";
-    public static final String  PROP_FILESYSTEM_FILE_EXTENSION   = ".log";
+    public static final String PROP_FILESYSTEM_DIR              = "dir";
+    public static final String PROP_FILESYSTEM_SUBDIR           = "subdir";
+    public static final String PROP_FILESYSTEM_FILE_NAME_FORMAT = 
"filename.format";
+    public static final String PROP_FILESYSTEM_FILE_ROLLOVER    = 
"file.rollover.sec";
+    public static final String PROP_FILESYSTEM_ROLLOVER_PERIOD  = 
"file.rollover.period";
+    public static final String PROP_FILESYSTEM_FILE_EXTENSION   = ".log";
+    public static final String PROP_IS_APPEND_ENABLED           = 
"file.append.enabled";
 
     public Configuration       conf;
     public FileSystem          fileSystem;
@@ -225,18 +226,19 @@ public void init(Properties props, String propPrefix) {
             logFileNameFormat = "%app-type%_ranger_audit_%hostname%" + 
fileExtension;
         }
 
-        logFolder = logFolderProp + "/" + logSubFolder;
+        reUseLastLogFile = MiscUtil.getBooleanProperty(props, propPrefix + "." 
+ PROP_IS_APPEND_ENABLED, false);
+        logFolder        = logFolderProp + "/" + logSubFolder;
 
-        logger.info("logFolder={}, destName={}", logFolder, auditProviderName);
-        logger.info("logFileNameFormat={}, destName={}", logFileNameFormat, 
auditProviderName);
-        logger.info("config={}", auditConfigs);
+        logger.info("logFolder = {}, destName = {}", logFolder, 
auditProviderName);
+        logger.info("logFileNameFormat = {}, destName = {}", 
logFileNameFormat, auditProviderName);
+        logger.info("config = {}", auditConfigs);
+        logger.info("isAppendEnabled = {}", reUseLastLogFile);
 
         rolloverPeriod  = MiscUtil.getStringProperty(props, propPrefix + "." + 
PROP_FILESYSTEM_ROLLOVER_PERIOD);
         rollingTimeUtil = RollingTimeUtil.getInstance();
 
-        //file.rollover.period is used for rolling over. If it could compute 
the next roll over time using file.rollover.period
-        //it fall back to use file.rollover.sec for find next rollover time. 
If still couldn't find default will be 1day window
-        //for rollover.
+        //file.rollover.period is used for rolling over. If it could compute 
the next rollover time using file.rollover.period
+        //it fallbacks to use file.rollover.sec for find next rollover time. 
If still couldn't find default will be 1day window for rollover.
         if (StringUtils.isEmpty(rolloverPeriod)) {
             rolloverPeriod = 
rollingTimeUtil.convertRolloverSecondsToRolloverPeriod(fileRolloverSec);
         }
@@ -272,7 +274,8 @@ public void closeFileIfNeeded() {
             setNextRollOverTime();
 
             currentFileName  = null;
-            reUseLastLogFile = false;
+            auditPath        = null;
+            fullPath         = null;
         }
 
         logger.debug("<== AbstractRangerAuditWriter.closeFileIfNeeded()");
@@ -290,13 +293,13 @@ public PrintWriter createWriter() throws Exception {
         if (logWriter == null) {
             boolean appendMode = false;
 
-            // if append is supported, reuse last log file
-            if (reUseLastLogFile && isAppendEnabled()) {
-                logger.info("Appending to last log file. auditPath = {}", 
fullPath);
-
+            // if append is supported and enabled via config param, reuse last 
log file
+            if (auditPath != null && reUseLastLogFile && isAppendEnabled()) {
                 try {
                     ostream    = fileSystem.append(auditPath);
                     appendMode = true;
+
+                    logger.info("Appending to last log file. auditPath = {}", 
fullPath);
                 } catch (Exception e) {
                     logger.error("Failed to append to file {} due to {}", 
fullPath, e.getMessage());
                     logger.info("Falling back to create a new log file!");
diff --git 
a/agents-audit/src/main/java/org/apache/ranger/audit/utils/RangerJSONAuditWriter.java
 
b/agents-audit/src/main/java/org/apache/ranger/audit/utils/RangerJSONAuditWriter.java
index e31770dbe..ab796b81d 100644
--- 
a/agents-audit/src/main/java/org/apache/ranger/audit/utils/RangerJSONAuditWriter.java
+++ 
b/agents-audit/src/main/java/org/apache/ranger/audit/utils/RangerJSONAuditWriter.java
@@ -119,8 +119,6 @@ public synchronized boolean logJSON(final 
Collection<String> events) throws Exce
                 closeWriter();
                 resetWriter();
 
-                reUseLastLogFile = true;
-
                 return false;
             }
         } catch (Exception e) {
@@ -128,8 +126,6 @@ public synchronized boolean logJSON(final 
Collection<String> events) throws Exce
             closeWriter();
             resetWriter();
 
-            reUseLastLogFile = true;
-
             return false;
         } finally {
             logger.debug("Flushing HDFS audit. Event Size:{}", events.size());
@@ -137,7 +133,6 @@ public synchronized boolean logJSON(final 
Collection<String> events) throws Exce
             if (out != null) {
                 out.flush();
             }
-            //closeWriter();
         }
 
         return true;
diff --git 
a/agents-audit/src/test/java/org/apache/ranger/audit/utils/RangerJSONAuditWriterTest.java
 
b/agents-audit/src/test/java/org/apache/ranger/audit/utils/RangerJSONAuditWriterTest.java
index df1f48e89..fbb2bbb68 100644
--- 
a/agents-audit/src/test/java/org/apache/ranger/audit/utils/RangerJSONAuditWriterTest.java
+++ 
b/agents-audit/src/test/java/org/apache/ranger/audit/utils/RangerJSONAuditWriterTest.java
@@ -18,21 +18,22 @@
 
 package org.apache.ranger.audit.utils;
 
-import org.apache.hadoop.fs.CommonPathCapabilities;
 import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
 import org.junit.Test;
 
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
@@ -47,77 +48,135 @@ public void setup() {
         props.setProperty("test.dir", "/tmp");
 
         auditConfigs.put(FileSystem.FS_DEFAULT_NAME_KEY, 
FileSystem.DEFAULT_FS);
+        auditConfigs.put("fs.file.impl", 
"org.apache.hadoop.fs.RawLocalFileSystem");
     }
 
     @Test
-    public void checkReUseFlagInStreamErrors() throws Exception {
+    public void verifyAppendToFileWhenEnabledWithConfig() throws Exception {
         RangerJSONAuditWriter jsonAuditWriter = spy(new 
RangerJSONAuditWriter());
-        PrintWriter           out             = mock(PrintWriter.class);
-
         setup();
+        props.setProperty("test.file.append.enabled", "true");
+        jsonAuditWriter.init(props, "test", "localfs", auditConfigs);
+
+        assertTrue(jsonAuditWriter.logJSON(Collections.singleton("This event 
will be logged in write(create) mode!")));
+        assertTrue(jsonAuditWriter.reUseLastLogFile);
+
+        when(jsonAuditWriter.getLogFileStream()).thenThrow(new 
IOException("Unable to fetch log file stream!"));
+        assertFalse(jsonAuditWriter.logJSON(Collections.singleton("This event 
will not be logged due to exception!")));
+
+        assertNull(jsonAuditWriter.ostream);
+        assertNull(jsonAuditWriter.logWriter);
+        assertNotNull(jsonAuditWriter.auditPath);
+        assertNotNull(jsonAuditWriter.fullPath);
+
+        reset(jsonAuditWriter);
+        assertTrue(jsonAuditWriter.logJSON(Collections.singleton("Last log 
file will be opened in append mode and this event will be written")));
+        assertTrue(jsonAuditWriter.logJSON(Collections.singleton("This event 
will also be written in append mode")));
 
+        jsonAuditWriter.fileSystem.deleteOnExit(jsonAuditWriter.auditPath);
+    }
+
+    @Test
+    public void verifyFileRolloverWithAppend() throws Exception {
+        RangerJSONAuditWriter jsonAuditWriter = spy(new 
RangerJSONAuditWriter());
+
+        setup();
+        props.setProperty("test.file.rollover.enable.periodic.rollover", 
"true");
+        props.setProperty("test.file.rollover.periodic.rollover.check.sec", 
"2");
+        props.setProperty("test.file.append.enabled", "true");
+        // rollover log file after this interval
+        jsonAuditWriter.fileRolloverSec = 5; // in seconds
         jsonAuditWriter.init(props, "test", "localfs", auditConfigs);
-        assertFalse(jsonAuditWriter.reUseLastLogFile);
 
-        when(jsonAuditWriter.getLogFileStream()).thenReturn(out);
-        when(out.checkError()).thenReturn(true);
-        assertFalse(jsonAuditWriter.logJSON(Collections.singleton("This event 
will not be logged!")));
         assertTrue(jsonAuditWriter.reUseLastLogFile);
+        assertTrue(jsonAuditWriter.logJSON(Collections.singleton("This event 
will be logged in write(create) mode!")));
+
+        when(jsonAuditWriter.getLogFileStream()).thenThrow(new 
IOException("Unable to fetch log file stream!"));
+        assertFalse(jsonAuditWriter.logJSON(Collections.singleton("This event 
will not be logged due to exception!")));
+
+        assertNull(jsonAuditWriter.ostream);
+        assertNull(jsonAuditWriter.logWriter);
+        assertNotNull(jsonAuditWriter.auditPath);
+        assertNotNull(jsonAuditWriter.fullPath);
+
+        reset(jsonAuditWriter);
+
+        assertTrue(jsonAuditWriter.logJSON(Collections.singleton("Last log 
file will be opened in append mode and this event will be written")));
+        assertTrue(jsonAuditWriter.logJSON(Collections.singleton("This event 
will also be written in append mode")));
+        Path auditPath1 = jsonAuditWriter.auditPath;
+
+        Thread.sleep(6000);
+
+        // rollover should have happened
+        assertTrue(jsonAuditWriter.reUseLastLogFile);
+        assertNull(jsonAuditWriter.ostream);
+        assertNull(jsonAuditWriter.logWriter);
+        assertNull(jsonAuditWriter.auditPath);
+        assertNull(jsonAuditWriter.fullPath);
+
+        assertTrue(jsonAuditWriter.logJSON(Collections.singleton("Second file 
created since rollover happened!")));
+
+        // ensure the same rolled over file is not used for append
+        assertNotEquals(auditPath1, jsonAuditWriter.auditPath);
 
         // cleanup
+        jsonAuditWriter.fileSystem.deleteOnExit(auditPath1);
         jsonAuditWriter.fileSystem.deleteOnExit(jsonAuditWriter.auditPath);
-        jsonAuditWriter.logJSON(Collections.singleton("cleaning up!"));
         jsonAuditWriter.closeWriter();
     }
 
     @Test
-    public void checkAppendtoFileWhenExceptionsOccur() throws Exception {
+    public void verifyNoAppendToFileWhenDisabledWithConfig() throws Exception {
         RangerJSONAuditWriter jsonAuditWriter = spy(new 
RangerJSONAuditWriter());
 
         setup();
-
+        props.setProperty("test.file.append.enabled", "false");
         jsonAuditWriter.init(props, "test", "localfs", auditConfigs);
         jsonAuditWriter.createFileSystemFolders();
-
         // File creation should fail with an exception which will trigger 
append next time.
-        
when(jsonAuditWriter.fileSystem.create(jsonAuditWriter.auditPath)).thenThrow(new
 IOException("Creation not allowed!"));
-        jsonAuditWriter.logJSON(Collections.singleton("This event will not be 
logged!"));
-        jsonAuditWriter.fileSystem.deleteOnExit(jsonAuditWriter.auditPath);
-        assertTrue(jsonAuditWriter.reUseLastLogFile);
+        
when(jsonAuditWriter.fileSystem.create(jsonAuditWriter.auditPath)).thenThrow(new
 IOException("Creation not allowed at this time!"));
+        assertFalse(jsonAuditWriter.logJSON(Collections.singleton("This event 
will not be logged!")));
+        assertFalse(jsonAuditWriter.reUseLastLogFile);
         assertNull(jsonAuditWriter.ostream);
         assertNull(jsonAuditWriter.logWriter);
+        assertNotNull(jsonAuditWriter.auditPath);
+        assertNotNull(jsonAuditWriter.fullPath);
 
-        jsonAuditWriter.fileSystem = mock(FileSystem.class);
-        
when(jsonAuditWriter.fileSystem.hasPathCapability(jsonAuditWriter.auditPath, 
CommonPathCapabilities.FS_APPEND)).thenReturn(true);
+        Path auditPath1 = jsonAuditWriter.auditPath;
+
+        reset(jsonAuditWriter);
+        assertTrue(jsonAuditWriter.logJSON(Collections.singleton("This event 
should be written to a newly created file in write mode!")));
+        assertTrue(jsonAuditWriter.logJSON(Collections.singleton("This event 
should also be written to the previous file")));
+        assertFalse(jsonAuditWriter.reUseLastLogFile);
+
+        // cleanup
+        jsonAuditWriter.fileSystem.deleteOnExit(auditPath1);
         jsonAuditWriter.fileSystem.deleteOnExit(jsonAuditWriter.auditPath);
-        // this will lead to an exception since append is called on mocks
-        jsonAuditWriter.logJSON(Collections.singleton("This event should be 
appended but won't be as appended we use mocks."));
     }
 
     @Test
-    public void checkFileRolloverAfterThreshold() throws Exception {
+    public void verifyFileRolloverAfterThreshold() throws Exception {
         RangerJSONAuditWriter jsonAuditWriter = spy(new 
RangerJSONAuditWriter());
 
         setup();
-
         props.setProperty("test.file.rollover.enable.periodic.rollover", 
"true");
         props.setProperty("test.file.rollover.periodic.rollover.check.sec", 
"2");
         // rollover log file after this interval
-
         jsonAuditWriter.fileRolloverSec = 5; // in seconds
         jsonAuditWriter.init(props, "test", "localfs", auditConfigs);
 
         assertTrue(jsonAuditWriter.logJSON(Collections.singleton("First file 
created and added this line!")));
+        Path auditPath1 = jsonAuditWriter.auditPath;
 
-        jsonAuditWriter.fileSystem.deleteOnExit(jsonAuditWriter.auditPath); // 
cleanup
         Thread.sleep(6000);
-        assertFalse(jsonAuditWriter.reUseLastLogFile);
+
         assertNull(jsonAuditWriter.ostream);
         assertNull(jsonAuditWriter.logWriter);
-
         assertTrue(jsonAuditWriter.logJSON(Collections.singleton("Second file 
created since rollover happened!")));
 
-        jsonAuditWriter.fileSystem.deleteOnExit(jsonAuditWriter.auditPath); // 
cleanup
+        // cleanup
+        jsonAuditWriter.fileSystem.deleteOnExit(auditPath1);
+        jsonAuditWriter.fileSystem.deleteOnExit(jsonAuditWriter.auditPath);
         jsonAuditWriter.closeWriter();
     }
 }

Reply via email to