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 =