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

mchades pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 793c7b9964 [#8752] feat(gvfs-java): add fallback for java gvfs (#8753)
793c7b9964 is described below

commit 793c7b99647d16352cbe8b283ef86b3bd58994a0
Author: Junda Yang <[email protected]>
AuthorDate: Thu Oct 23 02:40:42 2025 -0700

    [#8752] feat(gvfs-java): add fallback for java gvfs (#8753)
    
    ### What changes were proposed in this pull request?
    
    Add fallback support in java gvfs hooks
    
    ### Why are the changes needed?
    
    this is the last resort for fallback scenario, in case anything goes
    wrong with Gravitino server or gvfs.
    
    Fix: #8752
    
    ### Does this PR introduce _any_ user-facing change?
    
    By default, no behavior change. However, user now can customize
    fallback.
    
    ### How was this patch tested?
    
    Unit tests added and updated
---
 .../hadoop/GravitinoVirtualFileSystem.java         | 133 ++++++++-------
 .../hadoop/GravitinoVirtualFileSystemHook.java     | 143 ++++++++++++++++
 .../gravitino/filesystem/hadoop/NoOpHook.java      | 139 ++++++++++++++++
 .../gravitino/filesystem/hadoop/MockGVFSHook.java  |  93 ++++++++++-
 .../gravitino/filesystem/hadoop/TestGvfsBase.java  | 185 +++++++++++++++++++++
 5 files changed, 628 insertions(+), 65 deletions(-)

diff --git 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystem.java
 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystem.java
index 8b93d89d10..89a40bc7d9 100644
--- 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystem.java
+++ 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystem.java
@@ -130,29 +130,33 @@ public class GravitinoVirtualFileSystem extends 
FileSystem {
 
   @Override
   public synchronized void setWorkingDirectory(Path newDir) {
-    Path newPath = hook.preSetWorkingDirectory(newDir);
     try {
+      Path newPath = hook.preSetWorkingDirectory(newDir);
       runWithExceptionTranslation(
           () -> {
             operations.setWorkingDirectory(newPath);
             return null;
           },
           FilesetDataOperation.SET_WORKING_DIR);
-    } catch (FileNotFoundException e) {
-      throw new RuntimeException(e);
+      this.workingDirectory = newPath;
+      hook.postSetWorkingDirectory(newPath);
+    } catch (Exception e) {
+      hook.onSetWorkingDirectoryFailure(newDir, e);
     }
-    this.workingDirectory = newPath;
-    hook.postSetWorkingDirectory(newPath);
   }
 
   @Override
   public FSDataInputStream open(Path path, int bufferSize) throws IOException {
-    Path newPath = hook.preOpen(path, bufferSize);
-    return hook.postOpen(
-        newPath,
-        bufferSize,
-        runWithExceptionTranslation(
-            () -> operations.open(newPath, bufferSize), 
FilesetDataOperation.OPEN));
+    try {
+      Path newPath = hook.preOpen(path, bufferSize);
+      return hook.postOpen(
+          newPath,
+          bufferSize,
+          runWithExceptionTranslation(
+              () -> operations.open(newPath, bufferSize), 
FilesetDataOperation.OPEN));
+    } catch (Exception e) {
+      return hook.onOpenFailure(path, bufferSize, e);
+    }
   }
 
   @Override
@@ -165,8 +169,9 @@ public class GravitinoVirtualFileSystem extends FileSystem {
       long blockSize,
       Progressable progress)
       throws IOException {
-    Path newPath = hook.preCreate(path, permission, overwrite, bufferSize, 
replication, blockSize);
     try {
+      Path newPath =
+          hook.preCreate(path, permission, overwrite, bufferSize, replication, 
blockSize);
       return hook.postCreate(
           newPath,
           permission,
@@ -176,115 +181,115 @@ public class GravitinoVirtualFileSystem extends 
FileSystem {
           blockSize,
           operations.create(
               newPath, permission, overwrite, bufferSize, replication, 
blockSize, progress));
-    } catch (NoSuchCatalogException
-        | CatalogNotInUseException
-        | NoSuchFilesetException
-        | NoSuchLocationNameException e) {
-      String message =
-          "Fileset is not found for path: "
-              + path
-              + " for operation CREATE. "
-              + "This may be caused by fileset related metadata not found or 
not in use in "
-              + "Gravitino, please check the fileset metadata in Gravitino.";
-      throw new IOException(message, e);
+    } catch (Exception e) {
+      return hook.onCreateFailure(
+          path, permission, overwrite, bufferSize, replication, blockSize, 
progress, e);
     }
   }
 
   @Override
   public FSDataOutputStream append(Path path, int bufferSize, Progressable 
progress)
       throws IOException {
-    Path newPath = hook.preAppend(path, bufferSize);
-    return hook.postAppend(
-        newPath,
-        bufferSize,
-        runWithExceptionTranslation(
-            () -> operations.append(newPath, bufferSize, progress), 
FilesetDataOperation.APPEND));
+    try {
+      Path newPath = hook.preAppend(path, bufferSize);
+      return hook.postAppend(
+          newPath,
+          bufferSize,
+          runWithExceptionTranslation(
+              () -> operations.append(newPath, bufferSize, progress), 
FilesetDataOperation.APPEND));
+    } catch (Exception e) {
+      return hook.onAppendFailure(path, bufferSize, progress, e);
+    }
   }
 
   @Override
   public boolean rename(Path src, Path dst) throws IOException {
-    Pair<Path, Path> pair = hook.preRename(src, dst);
-    return hook.postRename(
-        pair.getLeft(),
-        pair.getRight(),
-        runWithExceptionTranslation(
-            () -> operations.rename(pair.getLeft(), pair.getRight()), 
FilesetDataOperation.RENAME));
+    try {
+      Pair<Path, Path> pair = hook.preRename(src, dst);
+      return hook.postRename(
+          pair.getLeft(),
+          pair.getRight(),
+          runWithExceptionTranslation(
+              () -> operations.rename(pair.getLeft(), pair.getRight()),
+              FilesetDataOperation.RENAME));
+    } catch (Exception e) {
+      return hook.onRenameFailure(src, dst, e);
+    }
   }
 
   @Override
   public boolean delete(Path path, boolean recursive) throws IOException {
-    Path newPath = hook.preDelete(path, recursive);
     try {
+      Path newPath = hook.preDelete(path, recursive);
       return hook.postDelete(
           newPath,
           recursive,
           runWithExceptionTranslation(
               () -> operations.delete(newPath, recursive), 
FilesetDataOperation.DELETE));
-    } catch (FileNotFoundException e) {
-      return false;
+    } catch (Exception e) {
+      return hook.onDeleteFailure(path, recursive, e);
     }
   }
 
   @Override
   public FileStatus getFileStatus(Path path) throws IOException {
-    Path newPath = hook.preGetFileStatus(path);
-    return hook.postGetFileStatus(
-        runWithExceptionTranslation(
-            () -> operations.getFileStatus(newPath), 
FilesetDataOperation.GET_FILE_STATUS));
+    try {
+      Path newPath = hook.preGetFileStatus(path);
+      return hook.postGetFileStatus(
+          runWithExceptionTranslation(
+              () -> operations.getFileStatus(newPath), 
FilesetDataOperation.GET_FILE_STATUS));
+    } catch (Exception e) {
+      return hook.onGetFileStatusFailure(path, e);
+    }
   }
 
   @Override
   public FileStatus[] listStatus(Path path) throws IOException {
-    Path newPath = hook.preListStatus(path);
-    return hook.postListStatus(
-        runWithExceptionTranslation(
-            () -> operations.listStatus(newPath), 
FilesetDataOperation.LIST_STATUS));
+    try {
+      Path newPath = hook.preListStatus(path);
+      return hook.postListStatus(
+          runWithExceptionTranslation(
+              () -> operations.listStatus(newPath), 
FilesetDataOperation.LIST_STATUS));
+    } catch (Exception e) {
+      return hook.onListStatusFailure(path, e);
+    }
   }
 
   @Override
   public boolean mkdirs(Path path, FsPermission permission) throws IOException 
{
-    Path newPath = hook.preMkdirs(path, permission);
     try {
+      Path newPath = hook.preMkdirs(path, permission);
       return hook.postMkdirs(newPath, permission, operations.mkdirs(newPath, 
permission));
-    } catch (NoSuchCatalogException
-        | CatalogNotInUseException
-        | NoSuchFilesetException
-        | NoSuchLocationNameException e) {
-      String message =
-          "Fileset is not found for path: "
-              + newPath
-              + " for operation MKDIRS. "
-              + "This may be caused by fileset related metadata not found or 
not in use in "
-              + "Gravitino, please check the fileset metadata in Gravitino.";
-      throw new IOException(message, e);
+    } catch (Exception e) {
+      return hook.onMkdirsFailure(path, permission, e);
     }
   }
 
   @Override
   public short getDefaultReplication(Path f) {
-    Path newPath = hook.preGetDefaultReplication(f);
     try {
+      Path newPath = hook.preGetDefaultReplication(f);
       return hook.postGetDefaultReplication(
           newPath,
           runWithExceptionTranslation(
               () -> operations.getDefaultReplication(newPath),
               FilesetDataOperation.GET_DEFAULT_REPLICATION));
-    } catch (IOException e) {
-      return 1;
+    } catch (Exception e) {
+      return hook.onGetDefaultReplicationFailure(f, e);
     }
   }
 
   @Override
   public long getDefaultBlockSize(Path f) {
-    Path newPath = hook.preGetDefaultBlockSize(f);
     try {
+      Path newPath = hook.preGetDefaultBlockSize(f);
       return hook.postGetDefaultBlockSize(
           newPath,
           runWithExceptionTranslation(
               () -> operations.getDefaultBlockSize(newPath),
               FilesetDataOperation.GET_DEFAULT_BLOCK_SIZE));
-    } catch (IOException e) {
-      return operations.defaultBlockSize();
+    } catch (Exception e) {
+      return hook.onGetDefaultBlockSizeFailure(f, e, 
operations.defaultBlockSize());
     }
   }
 
diff --git 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemHook.java
 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemHook.java
index a0978035ef..4e242761fa 100644
--- 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemHook.java
+++ 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemHook.java
@@ -19,6 +19,7 @@
 package org.apache.gravitino.filesystem.hadoop;
 
 import java.io.Closeable;
+import java.io.IOException;
 import java.util.Map;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.hadoop.fs.FSDataInputStream;
@@ -26,6 +27,7 @@ import org.apache.hadoop.fs.FSDataOutputStream;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.util.Progressable;
 
 /**
  * A hook interface for injecting custom logic before the Gravitino Virtual 
File System operations.
@@ -327,4 +329,145 @@ public interface GravitinoVirtualFileSystemHook extends 
Closeable {
    * @return The default block size.
    */
   long postGetDefaultBlockSize(Path gvfsPath, long blockSize);
+
+  /**
+   * Called when setting the working directory fails.
+   *
+   * @param path the path that was attempted to be set as working directory
+   * @param e the exception that caused the failure
+   */
+  void onSetWorkingDirectoryFailure(Path path, Exception e);
+
+  /**
+   * Called when opening a file fails.
+   *
+   * @param path the path of the file that failed to open
+   * @param bufferSize the buffer size that was requested
+   * @param e the exception that caused the failure
+   * @return the fallback input stream, or null if no fallback is available
+   * @throws IOException if an I/O error occurs.
+   */
+  FSDataInputStream onOpenFailure(Path path, int bufferSize, Exception e) 
throws IOException;
+
+  /**
+   * Called when creating a file fails.
+   *
+   * @param path the path of the file that failed to create
+   * @param permission the file permission that was requested
+   * @param overwrite whether to overwrite existing files
+   * @param bufferSize the buffer size that was requested
+   * @param replication the replication factor that was requested
+   * @param blockSize the block size that was requested
+   * @param progress the progress callback
+   * @param e the exception that caused the failure
+   * @return the fallback output stream, or null if no fallback is available
+   * @throws IOException if an I/O error occurs.
+   */
+  FSDataOutputStream onCreateFailure(
+      Path path,
+      FsPermission permission,
+      boolean overwrite,
+      int bufferSize,
+      short replication,
+      long blockSize,
+      Progressable progress,
+      Exception e)
+      throws IOException;
+
+  /**
+   * Called when appending to a file fails.
+   *
+   * @param path the path of the file that failed to append to
+   * @param bufferSize the buffer size that was requested
+   * @param progress the progress callback
+   * @param e the exception that caused the failure
+   * @return the fallback output stream, or null if no fallback is available
+   * @throws IOException if an I/O error occurs.
+   */
+  FSDataOutputStream onAppendFailure(Path path, int bufferSize, Progressable 
progress, Exception e)
+      throws IOException;
+
+  /**
+   * Called when renaming a file or directory fails.
+   *
+   * @param src the source path that failed to be renamed
+   * @param dst the destination path for the rename operation
+   * @param e the exception that caused the failure
+   * @return true if the fallback operation succeeded, false otherwise
+   * @throws IOException if an I/O error occurs.
+   */
+  boolean onRenameFailure(Path src, Path dst, Exception e) throws IOException;
+
+  /**
+   * Called when deleting a file or directory fails.
+   *
+   * @param path the path that failed to be deleted
+   * @param recursive whether the deletion was requested to be recursive
+   * @param e the exception that caused the failure
+   * @return true if the fallback operation succeeded, false otherwise
+   * @throws IOException if an I/O error occurs.
+   */
+  boolean onDeleteFailure(Path path, boolean recursive, Exception e) throws 
IOException;
+
+  /**
+   * Called when getting file status fails.
+   *
+   * @param path the path that failed to get status for
+   * @param e the exception that caused the failure
+   * @return the fallback file status, or null if no fallback is available
+   * @throws IOException if an I/O error occurs.
+   */
+  FileStatus onGetFileStatusFailure(Path path, Exception e) throws IOException;
+
+  /**
+   * Called when listing directory contents fails.
+   *
+   * @param path the path that failed to list contents for
+   * @param e the exception that caused the failure
+   * @return the fallback file status array, or null if no fallback is 
available
+   * @throws IOException if an I/O error occurs.
+   */
+  FileStatus[] onListStatusFailure(Path path, Exception e) throws IOException;
+
+  /**
+   * Called when creating directories fails.
+   *
+   * @param path the path that failed to create directories for
+   * @param permission the permission that was requested for the directories
+   * @param e the exception that caused the failure
+   * @return true if the fallback operation succeeded, false otherwise
+   * @throws IOException if an I/O error occurs.
+   */
+  boolean onMkdirsFailure(Path path, FsPermission permission, Exception e) 
throws IOException;
+
+  /**
+   * Called when getting default replication factor fails.
+   *
+   * @param path the path that failed to get replication factor for
+   * @param e the exception that caused the failure
+   * @return the fallback replication factor, or -1 if no fallback is available
+   */
+  short onGetDefaultReplicationFailure(Path path, Exception e);
+
+  /**
+   * Called when getting default block size fails.
+   *
+   * @param f the path that failed to get block size for
+   * @param e the exception that caused the failure
+   * @param defaultBlockSize the default block size value
+   * @return the fallback block size, or -1 if no fallback is available
+   */
+  long onGetDefaultBlockSizeFailure(Path f, Exception e, long 
defaultBlockSize);
+
+  /**
+   * Converts a checked exception to an unchecked RuntimeException. If the 
exception is already a
+   * RuntimeException, it is returned as-is. Otherwise, it is wrapped in a new 
RuntimeException with
+   * the original as the cause.
+   *
+   * @param e the exception to convert
+   * @return the unchecked exception
+   */
+  default RuntimeException asUnchecked(Exception e) {
+    return (e instanceof RuntimeException) ? (RuntimeException) e : new 
RuntimeException(e);
+  }
 }
diff --git 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/NoOpHook.java
 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/NoOpHook.java
index d1fff5478b..601be36a2c 100644
--- 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/NoOpHook.java
+++ 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/NoOpHook.java
@@ -18,14 +18,20 @@
  */
 package org.apache.gravitino.filesystem.hadoop;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.Map;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.gravitino.exceptions.CatalogNotInUseException;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchFilesetException;
+import org.apache.gravitino.exceptions.NoSuchLocationNameException;
 import org.apache.hadoop.fs.FSDataInputStream;
 import org.apache.hadoop.fs.FSDataOutputStream;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.util.Progressable;
 
 /**
  * The default implementation of {@link GravitinoVirtualFileSystemHook}. This 
class does nothing.
@@ -157,6 +163,139 @@ public class NoOpHook implements 
GravitinoVirtualFileSystemHook {
     return blockSize;
   }
 
+  @Override
+  public void onSetWorkingDirectoryFailure(Path path, Exception e) {
+    throw asUnchecked(e);
+  }
+
+  @Override
+  public FSDataInputStream onOpenFailure(Path path, int bufferSize, Exception 
e)
+      throws IOException {
+    if (e instanceof IOException) {
+      throw (IOException) e;
+    }
+    throw asUnchecked(e);
+  }
+
+  @Override
+  public FSDataOutputStream onCreateFailure(
+      Path path,
+      FsPermission permission,
+      boolean overwrite,
+      int bufferSize,
+      short replication,
+      long blockSize,
+      Progressable progress,
+      Exception e)
+      throws IOException {
+    if (e instanceof NoSuchCatalogException
+        || e instanceof CatalogNotInUseException
+        || e instanceof NoSuchFilesetException
+        || e instanceof NoSuchLocationNameException) {
+      String message =
+          "Fileset is not found for path: "
+              + path
+              + " for operation CREATE. "
+              + "This may be caused by fileset related metadata not found or 
not in use in "
+              + "Gravitino, please check the fileset metadata in Gravitino.";
+      throw new IOException(message, e);
+    }
+
+    if (e instanceof IOException) {
+      throw (IOException) e;
+    }
+
+    throw asUnchecked(e);
+  }
+
+  @Override
+  public FSDataOutputStream onAppendFailure(
+      Path path, int bufferSize, Progressable progress, Exception e) throws 
IOException {
+    if (e instanceof IOException) {
+      throw (IOException) e;
+    }
+
+    throw asUnchecked(e);
+  }
+
+  @Override
+  public boolean onRenameFailure(Path src, Path dst, Exception e) throws 
IOException {
+    if (e instanceof IOException) {
+      throw (IOException) e;
+    }
+    throw asUnchecked(e);
+  }
+
+  @Override
+  public boolean onDeleteFailure(Path path, boolean recursive, Exception e) 
throws IOException {
+    if (e instanceof FileNotFoundException) {
+      return false;
+    }
+
+    if (e instanceof IOException) {
+      throw (IOException) e;
+    }
+
+    throw asUnchecked(e);
+  }
+
+  @Override
+  public FileStatus onGetFileStatusFailure(Path path, Exception e) throws 
IOException {
+    if (e instanceof IOException) {
+      throw (IOException) e;
+    }
+    throw asUnchecked(e);
+  }
+
+  @Override
+  public FileStatus[] onListStatusFailure(Path path, Exception e) throws 
IOException {
+    if (e instanceof IOException) {
+      throw (IOException) e;
+    }
+    throw asUnchecked(e);
+  }
+
+  @Override
+  public boolean onMkdirsFailure(Path path, FsPermission permission, Exception 
e)
+      throws IOException {
+    if (e instanceof NoSuchCatalogException
+        || e instanceof CatalogNotInUseException
+        || e instanceof NoSuchFilesetException
+        || e instanceof NoSuchLocationNameException) {
+      String message =
+          "Fileset is not found for path: "
+              + path
+              + " for operation MKDIRS. "
+              + "This may be caused by fileset related metadata not found or 
not in use in "
+              + "Gravitino, please check the fileset metadata in Gravitino.";
+      throw new IOException(message, e);
+    }
+
+    if (e instanceof IOException) {
+      throw (IOException) e;
+    }
+
+    throw asUnchecked(e);
+  }
+
+  @Override
+  public short onGetDefaultReplicationFailure(Path path, Exception e) {
+    if (e instanceof IOException) {
+      return 1;
+    }
+
+    throw asUnchecked(e);
+  }
+
+  @Override
+  public long onGetDefaultBlockSizeFailure(Path f, Exception e, long 
defaultBlockSize) {
+    if (e instanceof IOException) {
+      return defaultBlockSize;
+    }
+
+    throw asUnchecked(e);
+  }
+
   @Override
   public void close() throws IOException {}
 }
diff --git 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/MockGVFSHook.java
 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/MockGVFSHook.java
index cf0413df4c..a12fa1a0b0 100644
--- 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/MockGVFSHook.java
+++ 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/MockGVFSHook.java
@@ -26,8 +26,9 @@ import org.apache.hadoop.fs.FSDataOutputStream;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.util.Progressable;
 
-public class MockGVFSHook implements GravitinoVirtualFileSystemHook {
+public class MockGVFSHook extends NoOpHook {
 
   boolean setOperationsContextCalled = false;
   BaseGVFSOperations operations = null;
@@ -53,6 +54,17 @@ public class MockGVFSHook implements 
GravitinoVirtualFileSystemHook {
   boolean postMkdirsCalled = false;
   boolean postGetDefaultReplicationCalled = false;
   boolean postGetDefaultBlockSizeCalled = false;
+  boolean onSetWorkingDirectoryFailureCalled = false;
+  boolean onOpenFailureCalled = false;
+  boolean onCreateFailureCalled = false;
+  boolean onAppendFailureCalled = false;
+  boolean onRenameFailureCalled = false;
+  boolean onDeleteFailureCalled = false;
+  boolean onGetFileStatusFailureCalled = false;
+  boolean onListStatusFailureCalled = false;
+  boolean onMkdirsFailureCalled = false;
+  boolean onGetDefaultReplicationFailureCalled = false;
+  boolean onGetDefaultBlockSizeFailureCalled = false;
 
   @Override
   public void setOperationsContext(BaseGVFSOperations operations) {
@@ -208,6 +220,85 @@ public class MockGVFSHook implements 
GravitinoVirtualFileSystemHook {
     return blockSize;
   }
 
+  @Override
+  public void onSetWorkingDirectoryFailure(Path path, Exception e) {
+    this.onSetWorkingDirectoryFailureCalled = true;
+    super.onSetWorkingDirectoryFailure(path, e);
+  }
+
+  @Override
+  public FSDataInputStream onOpenFailure(Path path, int bufferSize, Exception 
e)
+      throws IOException {
+    this.onOpenFailureCalled = true;
+    return super.onOpenFailure(path, bufferSize, e);
+  }
+
+  @Override
+  public FSDataOutputStream onCreateFailure(
+      Path path,
+      FsPermission permission,
+      boolean overwrite,
+      int bufferSize,
+      short replication,
+      long blockSize,
+      Progressable progress,
+      Exception e)
+      throws IOException {
+    this.onCreateFailureCalled = true;
+    return super.onCreateFailure(
+        path, permission, overwrite, bufferSize, replication, blockSize, 
progress, e);
+  }
+
+  @Override
+  public FSDataOutputStream onAppendFailure(
+      Path path, int bufferSize, Progressable progress, Exception e) throws 
IOException {
+    this.onAppendFailureCalled = true;
+    return super.onAppendFailure(path, bufferSize, progress, e);
+  }
+
+  @Override
+  public boolean onRenameFailure(Path src, Path dst, Exception e) throws 
IOException {
+    this.onRenameFailureCalled = true;
+    return super.onRenameFailure(src, dst, e);
+  }
+
+  @Override
+  public boolean onDeleteFailure(Path path, boolean recursive, Exception e) 
throws IOException {
+    this.onDeleteFailureCalled = true;
+    return super.onDeleteFailure(path, recursive, e);
+  }
+
+  @Override
+  public FileStatus onGetFileStatusFailure(Path path, Exception e) throws 
IOException {
+    this.onGetFileStatusFailureCalled = true;
+    return super.onGetFileStatusFailure(path, e);
+  }
+
+  @Override
+  public FileStatus[] onListStatusFailure(Path path, Exception e) throws 
IOException {
+    this.onListStatusFailureCalled = true;
+    return super.onListStatusFailure(path, e);
+  }
+
+  @Override
+  public boolean onMkdirsFailure(Path path, FsPermission permission, Exception 
e)
+      throws IOException {
+    this.onMkdirsFailureCalled = true;
+    return super.onMkdirsFailure(path, permission, e);
+  }
+
+  @Override
+  public short onGetDefaultReplicationFailure(Path path, Exception e) {
+    this.onGetDefaultReplicationFailureCalled = true;
+    return super.onGetDefaultReplicationFailure(path, e);
+  }
+
+  @Override
+  public long onGetDefaultBlockSizeFailure(Path f, Exception e, long 
defaultBlockSize) {
+    this.onGetDefaultBlockSizeFailureCalled = true;
+    return super.onGetDefaultBlockSizeFailure(f, e, defaultBlockSize);
+  }
+
   @Override
   public void close() throws IOException {}
 }
diff --git 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestGvfsBase.java
 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestGvfsBase.java
index ef80150308..2d36f84e9b 100644
--- 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestGvfsBase.java
+++ 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestGvfsBase.java
@@ -41,6 +41,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyShort;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockserver.model.HttpRequest.request;
 import static org.mockserver.model.HttpResponse.response;
 
@@ -1083,6 +1084,190 @@ public class TestGvfsBase extends 
GravitinoMockServerBase {
     }
   }
 
+  @Test
+  public void testFailureHandlingMethodsAreCalled()
+      throws IOException, NoSuchFieldException, IllegalAccessException {
+    Assumptions.assumeTrue(getClass() == TestGvfsBase.class);
+    Configuration newConf = new Configuration(conf);
+    newConf.set(
+        GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_HOOK_CLASS,
+        MockGVFSHook.class.getCanonicalName());
+
+    try (GravitinoVirtualFileSystem fs =
+        (GravitinoVirtualFileSystem) new 
Path("gvfs://fileset/").getFileSystem(newConf)) {
+      BaseGVFSOperations mockOps = Mockito.mock(BaseGVFSOperations.class);
+
+      // Inject the mockOps
+      Field operationsField = 
GravitinoVirtualFileSystem.class.getDeclaredField("operations");
+      operationsField.setAccessible(true);
+      operationsField.set(fs, mockOps);
+
+      MockGVFSHook hook = getHook(fs);
+
+      // Test setWorkingDirectory failure
+      Mockito.doThrow(new RuntimeException("setWorkingDirectory failed"))
+          .when(mockOps)
+          .setWorkingDirectory(any());
+      assertThrows(
+          RuntimeException.class, () -> fs.setWorkingDirectory(new 
Path("gvfs://fileset/")));
+      assertTrue(
+          hook.onSetWorkingDirectoryFailureCalled, 
"onSetWorkingDirectoryFailure should be called");
+
+      // Test open failure
+      Mockito.doThrow(new RuntimeException("open 
failed")).when(mockOps).open(any(), anyInt());
+      assertThrows(RuntimeException.class, () -> fs.open(new 
Path("gvfs://fileset/"), 1024));
+      assertTrue(hook.onOpenFailureCalled, "onOpenFailure should be called");
+
+      // Test create failure
+      Mockito.doThrow(new RuntimeException("create failed"))
+          .when(mockOps)
+          .create(any(), any(), anyBoolean(), anyInt(), anyShort(), anyLong(), 
any());
+      assertThrows(RuntimeException.class, () -> fs.create(new 
Path("gvfs://fileset/"), true));
+      assertTrue(hook.onCreateFailureCalled, "onCreateFailure should be 
called");
+
+      // Test append failure
+      Mockito.doThrow(new RuntimeException("append failed"))
+          .when(mockOps)
+          .append(any(), anyInt(), any());
+      assertThrows(RuntimeException.class, () -> fs.append(new 
Path("gvfs://fileset/"), 1024));
+      assertTrue(hook.onAppendFailureCalled, "onAppendFailure should be 
called");
+
+      // Test rename failure
+      Mockito.doThrow(new RuntimeException("rename 
failed")).when(mockOps).rename(any(), any());
+      assertThrows(
+          RuntimeException.class,
+          () -> fs.rename(new Path("gvfs://fileset/"), new 
Path("gvfs://fileset/new")));
+      assertTrue(hook.onRenameFailureCalled, "onRenameFailure should be 
called");
+
+      // Test delete failure
+      Mockito.doThrow(new RuntimeException("delete failed"))
+          .when(mockOps)
+          .delete(any(), anyBoolean());
+      assertThrows(RuntimeException.class, () -> fs.delete(new 
Path("gvfs://fileset/"), true));
+      assertTrue(hook.onDeleteFailureCalled, "onDeleteFailure should be 
called");
+
+      // Test getFileStatus failure
+      Mockito.doThrow(new RuntimeException("getFileStatus failed"))
+          .when(mockOps)
+          .getFileStatus(any());
+      assertThrows(RuntimeException.class, () -> fs.getFileStatus(new 
Path("gvfs://fileset/")));
+      assertTrue(hook.onGetFileStatusFailureCalled, "onGetFileStatusFailure 
should be called");
+
+      // Test listStatus failure
+      Mockito.doThrow(new RuntimeException("listStatus 
failed")).when(mockOps).listStatus(any());
+      assertThrows(RuntimeException.class, () -> fs.listStatus(new 
Path("gvfs://fileset/")));
+      assertTrue(hook.onListStatusFailureCalled, "onListStatusFailure should 
be called");
+
+      // Test mkdirs failure
+      Mockito.doThrow(new RuntimeException("mkdirs 
failed")).when(mockOps).mkdirs(any(), any());
+      assertThrows(RuntimeException.class, () -> fs.mkdirs(new 
Path("gvfs://fileset/")));
+      assertTrue(hook.onMkdirsFailureCalled, "onMkdirsFailure should be 
called");
+
+      // Test getDefaultReplication failure
+      Mockito.doThrow(new RuntimeException("getDefaultReplication failed"))
+          .when(mockOps)
+          .getDefaultReplication(any());
+      assertThrows(
+          RuntimeException.class, () -> fs.getDefaultReplication(new 
Path("gvfs://fileset/")));
+      assertTrue(
+          hook.onGetDefaultReplicationFailureCalled,
+          "onGetDefaultReplicationFailure should be called");
+
+      // Test getDefaultBlockSize failure
+      Mockito.doThrow(new RuntimeException("getDefaultBlockSize failed"))
+          .when(mockOps)
+          .getDefaultBlockSize(any());
+      assertThrows(
+          RuntimeException.class, () -> fs.getDefaultBlockSize(new 
Path("gvfs://fileset/")));
+      assertTrue(
+          hook.onGetDefaultBlockSizeFailureCalled, 
"onGetDefaultBlockSizeFailure should be called");
+    }
+  }
+
+  @Test
+  public void testFailureHandlingWithSpecificExceptions()
+      throws IOException, NoSuchFieldException, IllegalAccessException {
+    Assumptions.assumeTrue(getClass() == TestGvfsBase.class);
+    Configuration newConf = new Configuration(conf);
+    newConf.set(
+        GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_HOOK_CLASS,
+        MockGVFSHook.class.getCanonicalName());
+
+    try (GravitinoVirtualFileSystem fs =
+        (GravitinoVirtualFileSystem) new 
Path("gvfs://fileset/").getFileSystem(newConf)) {
+      BaseGVFSOperations mockOps = Mockito.mock(BaseGVFSOperations.class);
+
+      // Inject the mockOps
+      Field operationsField = 
GravitinoVirtualFileSystem.class.getDeclaredField("operations");
+      operationsField.setAccessible(true);
+      operationsField.set(fs, mockOps);
+
+      MockGVFSHook hook = getHook(fs);
+
+      // Test with FileNotFoundException
+      Mockito.doThrow(new FileNotFoundException("File not found"))
+          .when(mockOps)
+          .open(any(), anyInt());
+      assertThrows(FileNotFoundException.class, () -> fs.open(new 
Path("gvfs://fileset/"), 1024));
+      assertTrue(
+          hook.onOpenFailureCalled, "onOpenFailure should be called with 
FileNotFoundException");
+
+      // Test with IOException
+      Mockito.doThrow(new IOException("IO error"))
+          .when(mockOps)
+          .create(any(), any(), anyBoolean(), anyInt(), anyShort(), anyLong(), 
any());
+      assertThrows(IOException.class, () -> fs.create(new 
Path("gvfs://fileset/"), true));
+      assertTrue(hook.onCreateFailureCalled, "onCreateFailure should be called 
with IOException");
+
+      // Test with SecurityException
+      Mockito.doThrow(new SecurityException("Security violation"))
+          .when(mockOps)
+          .delete(any(), anyBoolean());
+      assertThrows(SecurityException.class, () -> fs.delete(new 
Path("gvfs://fileset/"), true));
+      assertTrue(
+          hook.onDeleteFailureCalled, "onDeleteFailure should be called with 
SecurityException");
+    }
+  }
+
+  @Test
+  public void testFailureHandlingMethodParameters()
+      throws IOException, NoSuchFieldException, IllegalAccessException {
+    Assumptions.assumeTrue(getClass() == TestGvfsBase.class);
+    Configuration newConf = new Configuration(conf);
+    newConf.set(
+        GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_HOOK_CLASS,
+        MockGVFSHook.class.getCanonicalName());
+
+    try (GravitinoVirtualFileSystem fs =
+        (GravitinoVirtualFileSystem) new 
Path("gvfs://fileset/").getFileSystem(newConf)) {
+      BaseGVFSOperations mockOps = Mockito.mock(BaseGVFSOperations.class);
+
+      // Inject the mockOps
+      Field operationsField = 
GravitinoVirtualFileSystem.class.getDeclaredField("operations");
+      operationsField.setAccessible(true);
+      operationsField.set(fs, mockOps);
+
+      MockGVFSHook hook = getHook(fs);
+
+      Path testPath = new Path("gvfs://fileset/test");
+      RuntimeException testException = new RuntimeException("Test exception");
+
+      // Test that failure methods receive correct parameters
+      
Mockito.doThrow(testException).when(mockOps).setWorkingDirectory(testPath);
+      assertThrows(RuntimeException.class, () -> 
fs.setWorkingDirectory(testPath));
+      assertTrue(
+          hook.onSetWorkingDirectoryFailureCalled, 
"onSetWorkingDirectoryFailure should be called");
+
+      // Test with specific parameters for create
+      Mockito.doThrow(testException)
+          .when(mockOps)
+          .create(eq(testPath), any(), eq(true), eq(1024), eq((short) 1), 
eq(128L), any());
+      assertThrows(RuntimeException.class, () -> fs.create(testPath, true, 
1024, (short) 1, 128L));
+      assertTrue(
+          hook.onCreateFailureCalled, "onCreateFailure should be called with 
correct parameters");
+    }
+  }
+
   private void buildMockResourceForCredential(String filesetName, String 
filesetLocation)
       throws JsonProcessingException {
     String filesetPath =


Reply via email to