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 0e0a19e026 [#8948]Improve(gvfs-java) Lazy load gravitino client in 
gvfs (#8950)
0e0a19e026 is described below

commit 0e0a19e0266cb0a87a8137bee012602206299756
Author: Junda Yang <[email protected]>
AuthorDate: Tue Oct 28 23:10:49 2025 -0700

    [#8948]Improve(gvfs-java) Lazy load gravitino client in gvfs (#8950)
    
    ### What changes were proposed in this pull request?
    
    - Lazy load GravitinoClient and FilesetMetadataCache in
    BaseGVFSOperations using double-checked locking pattern
    - Client and cache are now created on first filesystem operation instead
    of during construction
    - Fixed resource ownership: FilesetMetadataCache no longer closes the
    shared GravitinoClient
    
    ### Why are the changes needed?
    
    - Improves startup performance by deferring expensive client
    initialization
    - Reduces resource usage when filesystem is created but never used
    - Better resource management with clear ownership (only
    BaseGVFSOperations closes the client)
    
    Fix: #8948
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, minor behavioral change:
    Configuration validation and connection errors are now deferred until
    the first filesystem operation (e.g., exists(), listStatus()) instead of
    being thrown during filesystem initialization.
    
    ### How was this patch tested?
    
    unit tests added and updated
---
 .../filesystem/hadoop/BaseGVFSOperations.java      |  82 +++++--
 .../filesystem/hadoop/FilesetMetadataCache.java    |   9 +-
 .../TestClientAndCacheLazyLoadingBehavior.java     | 253 +++++++++++++++++++++
 .../hadoop/TestFilesetMetadataCache.java           |  96 ++++++++
 .../gravitino/filesystem/hadoop/TestGvfsBase.java  |  15 +-
 .../filesystem/hadoop/TestKerberosClient.java      |  39 +++-
 .../filesystem/hadoop/TestOauth2Client.java        |  59 ++++-
 .../filesystem/hadoop/TestSimpleClient.java        |   8 +-
 8 files changed, 524 insertions(+), 37 deletions(-)

diff --git 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/BaseGVFSOperations.java
 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/BaseGVFSOperations.java
index 1fc0bc6b80..920ed90128 100644
--- 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/BaseGVFSOperations.java
+++ 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/BaseGVFSOperations.java
@@ -114,8 +114,14 @@ public abstract class BaseGVFSOperations implements 
Closeable {
 
   private final String metalakeName;
 
-  private final Optional<FilesetMetadataCache> filesetMetadataCache;
-  private final GravitinoClient gravitinoClient;
+  private final boolean enableFilesetMetadataCache;
+
+  // Lazy initialization of FilesetCatalogCache, see getFilesetMetadataCache() 
for details.
+  private volatile Optional<FilesetMetadataCache> filesetMetadataCache;
+  private final Object filesetMetadataCacheLock = new Object();
+
+  // Lazy initialization of GravitinoClient, see getGravitinoClient() for 
details.
+  private volatile GravitinoClient gravitinoClient;
 
   private final Configuration conf;
 
@@ -148,15 +154,10 @@ public abstract class BaseGVFSOperations implements 
Closeable {
         "'%s' is not set in the configuration",
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_METALAKE_KEY);
 
-    this.gravitinoClient = 
GravitinoVirtualFileSystemUtils.createClient(configuration);
-    boolean enableFilesetCatalogCache =
+    this.enableFilesetMetadataCache =
         configuration.getBoolean(
             FS_GRAVITINO_FILESET_METADATA_CACHE_ENABLE,
             FS_GRAVITINO_FILESET_METADATA_CACHE_ENABLE_DEFAULT);
-    this.filesetMetadataCache =
-        enableFilesetCatalogCache
-            ? Optional.of(new FilesetMetadataCache(gravitinoClient))
-            : Optional.empty();
 
     this.internalFileSystemCache = newFileSystemCache(configuration);
 
@@ -187,6 +188,26 @@ public abstract class BaseGVFSOperations implements 
Closeable {
     this.conf = configuration;
   }
 
+  /**
+   * Lazy initialization of FilesetMetadataCache, see 
getFilesetMetadataCache() for details.
+   *
+   * @return the FilesetMetadataCache.
+   */
+  @VisibleForTesting
+  protected Optional<FilesetMetadataCache> getFilesetMetadataCache() {
+    if (filesetMetadataCache == null) {
+      synchronized (filesetMetadataCacheLock) {
+        if (filesetMetadataCache == null) {
+          this.filesetMetadataCache =
+              enableFilesetMetadataCache
+                  ? Optional.of(new FilesetMetadataCache(getGravitinoClient()))
+                  : Optional.empty();
+        }
+      }
+    }
+    return filesetMetadataCache;
+  }
+
   @Override
   public void close() throws IOException {
     // close all actual FileSystems
@@ -200,12 +221,21 @@ public abstract class BaseGVFSOperations implements 
Closeable {
     internalFileSystemCache.invalidateAll();
 
     try {
-      if (filesetMetadataCache.isPresent()) {
+      if (filesetMetadataCache != null && filesetMetadataCache.isPresent()) {
         filesetMetadataCache.get().close();
       }
     } catch (IOException e) {
       // ignore
     }
+
+    // Close the GravitinoClient if it was initialized
+    try {
+      if (gravitinoClient != null) {
+        gravitinoClient.close();
+      }
+    } catch (Exception e) {
+      // ignore
+    }
   }
 
   /**
@@ -548,9 +578,9 @@ public abstract class BaseGVFSOperations implements 
Closeable {
    * @return the fileset catalog.
    */
   protected FilesetCatalog getFilesetCatalog(NameIdentifier catalogIdent) {
-    return filesetMetadataCache
+    return getFilesetMetadataCache()
         .map(cache -> cache.getFilesetCatalog(catalogIdent))
-        .orElseGet(() -> 
gravitinoClient.loadCatalog(catalogIdent.name()).asFilesetCatalog());
+        .orElseGet(() -> 
getGravitinoClient().loadCatalog(catalogIdent.name()).asFilesetCatalog());
   }
 
   /**
@@ -561,7 +591,7 @@ public abstract class BaseGVFSOperations implements 
Closeable {
    * @return the fileset.
    */
   protected Fileset getFileset(NameIdentifier filesetIdent) {
-    return filesetMetadataCache
+    return getFilesetMetadataCache()
         .map(cache -> cache.getFileset(filesetIdent))
         .orElseGet(
             () ->
@@ -577,6 +607,24 @@ public abstract class BaseGVFSOperations implements 
Closeable {
     return internalFileSystemCache;
   }
 
+  /**
+   * Lazy initialization of GravitinoClient using double-checked locking 
pattern. This ensures the
+   * expensive client creation only happens when actually needed.
+   *
+   * @return the GravitinoClient
+   */
+  @VisibleForTesting
+  GravitinoClient getGravitinoClient() {
+    if (gravitinoClient == null) {
+      synchronized (this) {
+        if (gravitinoClient == null) {
+          this.gravitinoClient = 
GravitinoVirtualFileSystemUtils.createClient(conf);
+        }
+      }
+    }
+    return gravitinoClient;
+  }
+
   private void setCallerContextForGetFileLocation(FilesetDataOperation 
operation) {
     Map<String, String> contextMap = Maps.newHashMap();
     contextMap.put(
@@ -594,7 +642,15 @@ public abstract class BaseGVFSOperations implements 
Closeable {
     CallerContext.CallerContextHolder.set(callerContext);
   }
 
-  private FileSystem getActualFileSystemByLocationName(
+  /**
+   * Get the actual file system corresponding to the given fileset identifier 
and location name.
+   *
+   * @param filesetIdent the fileset identifier.
+   * @param locationName the location name. null means the default location.
+   * @return the actual file system.
+   * @throws FileNotFoundException if the target location name is not found in 
the fileset.
+   */
+  protected FileSystem getActualFileSystemByLocationName(
       NameIdentifier filesetIdent, String locationName) throws 
FileNotFoundException {
     NameIdentifier catalogIdent =
         NameIdentifier.of(filesetIdent.namespace().level(0), 
filesetIdent.namespace().level(1));
diff --git 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/FilesetMetadataCache.java
 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/FilesetMetadataCache.java
index 2972e31142..239e1a144b 100644
--- 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/FilesetMetadataCache.java
+++ 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/FilesetMetadataCache.java
@@ -118,13 +118,6 @@ public class FilesetMetadataCache implements Closeable {
   public void close() throws IOException {
     catalogCache.invalidateAll();
     filesetCache.invalidateAll();
-    // close the client
-    try {
-      if (client != null) {
-        client.close();
-      }
-    } catch (Exception e) {
-      // ignore
-    }
+    // Note: We don't close the client here since it's owned and managed by 
BaseGVFSOperations
   }
 }
diff --git 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestClientAndCacheLazyLoadingBehavior.java
 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestClientAndCacheLazyLoadingBehavior.java
new file mode 100644
index 0000000000..66319c689f
--- /dev/null
+++ 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestClientAndCacheLazyLoadingBehavior.java
@@ -0,0 +1,253 @@
+/*
+ * 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.gravitino.filesystem.hadoop;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Optional;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.hadoop.conf.Configuration;
+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.security.Credentials;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.util.Progressable;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for lazy loading functionality in {@link BaseGVFSOperations}, 
verifying that
+ * GravitinoClient and FilesetMetadataCache are NOT created during 
construction and remain null
+ * until first access. These tests use reflection to verify the lazy 
initialization pattern without
+ * requiring an actual Gravitino server.
+ */
+public class TestClientAndCacheLazyLoadingBehavior {
+
+  /** A minimal concrete implementation of BaseGVFSOperations for testing lazy 
loading behavior. */
+  private static class TestableGVFSOperations extends BaseGVFSOperations {
+    protected TestableGVFSOperations(Configuration configuration) {
+      super(configuration);
+    }
+
+    // Minimal implementations - not used in lazy loading tests
+    @Override
+    public FSDataInputStream open(Path gvfsPath, int bufferSize) throws 
IOException {
+      throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public void setWorkingDirectory(Path gvfsDir) {
+      throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public FSDataOutputStream create(
+        Path gvfsPath,
+        FsPermission permission,
+        boolean overwrite,
+        int bufferSize,
+        short replication,
+        long blockSize,
+        Progressable progress)
+        throws IOException {
+      throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public FSDataOutputStream append(Path gvfsPath, int bufferSize, 
Progressable progress)
+        throws IOException {
+      throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public boolean rename(Path srcGvfsPath, Path dstGvfsPath) throws 
IOException {
+      throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public boolean delete(Path gvfsPath, boolean recursive) throws IOException 
{
+      throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public FileStatus getFileStatus(Path gvfsPath) throws IOException {
+      throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public FileStatus[] listStatus(Path gvfsPath) throws IOException {
+      throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public boolean mkdirs(Path gvfsPath, FsPermission permission) throws 
IOException {
+      throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public short getDefaultReplication(Path gvfsPath) {
+      return 1;
+    }
+
+    @Override
+    public long getDefaultBlockSize(Path gvfsPath) {
+      return 134217728L;
+    }
+
+    @Override
+    public Token<?>[] addDelegationTokens(String renewer, Credentials 
credentials) {
+      return new Token<?>[0];
+    }
+  }
+
+  private Configuration createTestConfiguration() {
+    Configuration conf = new Configuration();
+    conf.set(
+        
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_METALAKE_KEY, 
"test_metalake");
+    conf.set(
+        GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_SERVER_URI_KEY,
+        "http://localhost:8090";);
+    return conf;
+  }
+
+  /**
+   * Test that GravitinoClient is NOT created during BaseGVFSOperations 
construction.
+   *
+   * <p>This verifies the lazy initialization behavior - the expensive client 
creation should be
+   * deferred until first use.
+   */
+  @Test
+  public void testClientNotCreatedDuringConstruction() throws Exception {
+    Configuration conf = createTestConfiguration();
+    TestableGVFSOperations operations = new TestableGVFSOperations(conf);
+
+    // Access the private gravitinoClient field via reflection
+    Field clientField = 
BaseGVFSOperations.class.getDeclaredField("gravitinoClient");
+    clientField.setAccessible(true);
+    GravitinoClient client = (GravitinoClient) clientField.get(operations);
+
+    // Client should be null - not yet initialized
+    assertNull(client, "GravitinoClient should not be created during 
construction");
+
+    operations.close();
+  }
+
+  /**
+   * Test that FilesetMetadataCache is NOT created during construction.
+   *
+   * <p>This verifies lazy initialization of the cache.
+   */
+  @Test
+  public void testCacheNotCreatedDuringConstruction() throws Exception {
+    Configuration conf = createTestConfiguration();
+    conf.setBoolean(
+        
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_FILESET_METADATA_CACHE_ENABLE,
 true);
+
+    TestableGVFSOperations operations = new TestableGVFSOperations(conf);
+
+    // Access the private filesetMetadataCache field
+    Field cacheField = 
BaseGVFSOperations.class.getDeclaredField("filesetMetadataCache");
+    cacheField.setAccessible(true);
+    Optional<?> cache = (Optional<?>) cacheField.get(operations);
+
+    // Cache should be null - not yet initialized
+    assertNull(cache, "FilesetMetadataCache should not be created during 
construction");
+
+    operations.close();
+  }
+
+  /**
+   * Test that FilesetMetadataCache returns empty Optional when disabled.
+   *
+   * <p>This verifies that when disabled, the cache initialization creates an 
empty Optional rather
+   * than attempting to create a cache with a client.
+   */
+  @Test
+  public void testCacheNotCreatedWhenDisabled() throws Exception {
+    Configuration conf = createTestConfiguration();
+    conf.setBoolean(
+        
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_FILESET_METADATA_CACHE_ENABLE,
 false);
+
+    TestableGVFSOperations operations = new TestableGVFSOperations(conf);
+
+    // Access the cache - should return empty without trying to create a client
+    Optional<FilesetMetadataCache> cache = 
operations.getFilesetMetadataCache();
+
+    assertFalse(cache.isPresent(), "FilesetMetadataCache should not be created 
when disabled");
+
+    // Verify the field is now set (to Optional.empty())
+    Field cacheField = 
BaseGVFSOperations.class.getDeclaredField("filesetMetadataCache");
+    cacheField.setAccessible(true);
+    Optional<?> fieldCache = (Optional<?>) cacheField.get(operations);
+    assertNotNull(fieldCache, "Cache field should be set to Optional after 
first access");
+    assertFalse(fieldCache.isPresent(), "Cache Optional should be empty when 
disabled");
+
+    operations.close();
+  }
+
+  /**
+   * Test that closing BaseGVFSOperations without ever accessing the client 
completes safely.
+   *
+   * <p>This verifies that the lazy initialization doesn't break cleanup when 
resources are never
+   * used.
+   */
+  @Test
+  public void testCloseWithoutClientAccess() throws Exception {
+    Configuration conf = createTestConfiguration();
+    TestableGVFSOperations operations = new TestableGVFSOperations(conf);
+
+    // Close without ever accessing the client
+    operations.close();
+
+    // Verify client is still null
+    Field clientField = 
BaseGVFSOperations.class.getDeclaredField("gravitinoClient");
+    clientField.setAccessible(true);
+    GravitinoClient client = (GravitinoClient) clientField.get(operations);
+    assertNull(client, "Client should remain null if never accessed");
+  }
+
+  /**
+   * Test that closing BaseGVFSOperations without ever accessing the cache 
completes safely.
+   *
+   * <p>This verifies lazy initialization doesn't break cleanup for the cache.
+   */
+  @Test
+  public void testCloseWithoutCacheAccess() throws Exception {
+    Configuration conf = createTestConfiguration();
+    conf.setBoolean(
+        
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_FILESET_METADATA_CACHE_ENABLE,
 true);
+
+    TestableGVFSOperations operations = new TestableGVFSOperations(conf);
+
+    // Close without ever accessing the cache
+    operations.close();
+
+    // Verify cache is still null
+    Field cacheField = 
BaseGVFSOperations.class.getDeclaredField("filesetMetadataCache");
+    cacheField.setAccessible(true);
+    Optional<?> cache = (Optional<?>) cacheField.get(operations);
+    assertNull(cache, "Cache should remain null if never accessed");
+  }
+}
diff --git 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestFilesetMetadataCache.java
 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestFilesetMetadataCache.java
new file mode 100644
index 0000000000..3c04482fef
--- /dev/null
+++ 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestFilesetMetadataCache.java
@@ -0,0 +1,96 @@
+/*
+ * 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.gravitino.filesystem.hadoop;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+import org.apache.gravitino.client.GravitinoClient;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link FilesetMetadataCache} to verify proper resource management 
and client ownership
+ * semantics.
+ */
+public class TestFilesetMetadataCache {
+
+  /**
+   * Test that FilesetMetadataCache does NOT close the GravitinoClient when 
closed.
+   *
+   * <p>This verifies the resource ownership principle: FilesetMetadataCache 
receives a shared
+   * reference to the client but doesn't own it, so it should not close it. 
The owner
+   * (BaseGVFSOperations) is responsible for closing the client.
+   */
+  @Test
+  public void testCacheDoesNotCloseClient() throws IOException {
+    // Create a mock GravitinoClient
+    GravitinoClient mockClient = mock(GravitinoClient.class);
+
+    // Create the cache with the mock client
+    FilesetMetadataCache cache = new FilesetMetadataCache(mockClient);
+
+    // Close the cache
+    cache.close();
+
+    // Verify that the client's close() method was NEVER called
+    verify(mockClient, never()).close();
+  }
+
+  /**
+   * Test that FilesetMetadataCache can be closed multiple times safely.
+   *
+   * <p>Since the cache doesn't close the client, multiple close() calls 
should be idempotent and
+   * not cause any issues.
+   */
+  @Test
+  public void testCacheCanBeClosedMultipleTimes() throws IOException {
+    GravitinoClient mockClient = mock(GravitinoClient.class);
+    FilesetMetadataCache cache = new FilesetMetadataCache(mockClient);
+
+    // Close multiple times - should not throw any exceptions
+    cache.close();
+    cache.close();
+    cache.close();
+
+    // Client should still never be closed
+    verify(mockClient, never()).close();
+  }
+
+  /**
+   * Test that FilesetMetadataCache properly invalidates its internal caches 
when closed.
+   *
+   * <p>Even though the client isn't closed, the cache should properly clean 
up its internal
+   * Caffeine caches.
+   */
+  @Test
+  public void testCacheInvalidatesInternalCaches() throws IOException {
+    GravitinoClient mockClient = mock(GravitinoClient.class);
+    FilesetMetadataCache cache = new FilesetMetadataCache(mockClient);
+
+    // Note: We can't easily verify cache invalidation without accessing 
private fields,
+    // but we can at least verify that close() completes without errors
+    cache.close();
+
+    // If we got here without exceptions, the test passes
+    assertTrue(true, "Cache close completed successfully");
+  }
+}
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 0956ae2b8f..b3ae42dba4 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
@@ -290,6 +290,11 @@ public class TestGvfsBase extends GravitinoMockServerBase {
                 .withBody(getJsonString(new 
VersionResponse(Version.getCurrentVersionDTO()))));
 
     try (FileSystem fs = new 
Path("gvfs://fileset/").getFileSystem(configuration)) {
+      // Trigger lazy initialization by accessing a path (throws RESTException 
for mock server 404)
+      assertThrows(
+          RESTException.class,
+          () -> fs.exists(new 
Path("gvfs://fileset/catalog/schema/fileset/file.txt")));
+      // Verify the request was made with correct headers during client 
initialization
       mockServer().verify(req, VerificationTimes.once());
     }
   }
@@ -1011,7 +1016,10 @@ public class TestGvfsBase extends 
GravitinoMockServerBase {
         Assertions.assertThrows(
             IllegalArgumentException.class,
             () -> {
-              try (FileSystem fs = new 
Path("gvfs://fileset/").getFileSystem(configuration)) {}
+              try (FileSystem fs = new 
Path("gvfs://fileset/").getFileSystem(configuration)) {
+                // Trigger lazy initialization by accessing a path
+                fs.exists(new 
Path("gvfs://fileset/catalog/schema/fileset/file.txt"));
+              }
             });
     Assertions.assertEquals(
         "Invalid property for client: gravitino.client.xxxx", 
throwable.getMessage());
@@ -1037,7 +1045,10 @@ public class TestGvfsBase extends 
GravitinoMockServerBase {
         Assertions.assertThrows(
             RESTException.class,
             () -> {
-              try (FileSystem fs = new 
Path("gvfs://fileset/").getFileSystem(configuration)) {}
+              try (FileSystem fs = new 
Path("gvfs://fileset/").getFileSystem(configuration)) {
+                // Trigger lazy initialization by accessing a path
+                fs.exists(new 
Path("gvfs://fileset/catalog/schema/fileset/file.txt"));
+              }
             });
     Assertions.assertInstanceOf(SocketTimeoutException.class, 
throwable.getCause());
     Assertions.assertEquals("Read timed out", 
throwable.getCause().getMessage());
diff --git 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestKerberosClient.java
 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestKerberosClient.java
index 564b05cee7..b7e4ea3a38 100644
--- 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestKerberosClient.java
+++ 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestKerberosClient.java
@@ -31,6 +31,7 @@ import java.util.UUID;
 import org.apache.gravitino.Config;
 import org.apache.gravitino.server.authentication.KerberosAuthenticator;
 import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.Method;
@@ -90,14 +91,24 @@ public class TestKerberosClient extends TestGvfsBase {
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_AUTH_TYPE_KEY,
         GravitinoVirtualFileSystemConfiguration.KERBEROS_AUTH_TYPE);
     assertThrows(
-        IllegalArgumentException.class, () -> 
managedFilesetPath.getFileSystem(configuration));
+        IllegalArgumentException.class,
+        () -> {
+          FileSystem fs = managedFilesetPath.getFileSystem(configuration);
+          // Trigger lazy initialization
+          fs.exists(managedFilesetPath);
+        });
 
     // set not exist keytab path
     configuration.set(
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_KEYTAB_FILE_PATH_KEY,
         "file://tmp/test.keytab");
     assertThrows(
-        IllegalArgumentException.class, () -> 
managedFilesetPath.getFileSystem(configuration));
+        IllegalArgumentException.class,
+        () -> {
+          FileSystem fs = managedFilesetPath.getFileSystem(configuration);
+          // Trigger lazy initialization
+          fs.exists(managedFilesetPath);
+        });
   }
 
   @Test
@@ -184,7 +195,13 @@ public class TestKerberosClient extends TestGvfsBase {
               return response().withStatusCode(HttpStatus.SC_OK);
             });
     Path newPath = new 
Path(managedFilesetPath.toString().replace(metalakeName, testMetalake));
-    Assertions.assertThrows(IllegalStateException.class, () -> 
newPath.getFileSystem(conf1));
+    Assertions.assertThrows(
+        IllegalStateException.class,
+        () -> {
+          FileSystem fs = newPath.getFileSystem(conf1);
+          // Trigger lazy initialization
+          fs.exists(newPath);
+        });
 
     // test with principal and invalid keytab
     File invalidKeytabFile =
@@ -201,7 +218,13 @@ public class TestKerberosClient extends TestGvfsBase {
     conf2.set(
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_KEYTAB_FILE_PATH_KEY,
         invalidKeytabFile.getAbsolutePath());
-    Assertions.assertThrows(IllegalStateException.class, () -> 
newPath.getFileSystem(conf2));
+    Assertions.assertThrows(
+        IllegalStateException.class,
+        () -> {
+          FileSystem fs = newPath.getFileSystem(conf2);
+          // Trigger lazy initialization
+          fs.exists(newPath);
+        });
     invalidKeytabFile.delete();
 
     // test with principal and no keytab
@@ -212,6 +235,12 @@ public class TestKerberosClient extends TestGvfsBase {
     // remove keytab configuration
     conf3.unset(
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_KEYTAB_FILE_PATH_KEY);
-    Assertions.assertThrows(IllegalStateException.class, () -> 
newPath.getFileSystem(conf3));
+    Assertions.assertThrows(
+        IllegalStateException.class,
+        () -> {
+          FileSystem fs = newPath.getFileSystem(conf3);
+          // Trigger lazy initialization
+          fs.exists(newPath);
+        });
   }
 }
diff --git 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestOauth2Client.java
 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestOauth2Client.java
index 2186f53067..e673e1e1b0 100644
--- 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestOauth2Client.java
+++ 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestOauth2Client.java
@@ -56,6 +56,7 @@ import org.apache.gravitino.rest.RESTUtils;
 import org.apache.gravitino.server.authentication.OAuthConfig;
 import org.apache.gravitino.server.authentication.ServerAuthenticator;
 import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.Method;
@@ -180,27 +181,47 @@ public class TestOauth2Client extends TestGvfsBase {
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_AUTH_TYPE_KEY,
         GravitinoVirtualFileSystemConfiguration.OAUTH2_AUTH_TYPE);
     assertThrows(
-        IllegalArgumentException.class, () -> 
managedFilesetPath.getFileSystem(configuration));
+        IllegalArgumentException.class,
+        () -> {
+          FileSystem fs = managedFilesetPath.getFileSystem(configuration);
+          // Trigger lazy initialization
+          fs.exists(managedFilesetPath);
+        });
 
     // set oauth server uri, but do not set other configs
     configuration.set(
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_OAUTH2_SERVER_URI_KEY,
         Oauth2MockServerBase.serverUri());
     assertThrows(
-        IllegalArgumentException.class, () -> 
managedFilesetPath.getFileSystem(configuration));
+        IllegalArgumentException.class,
+        () -> {
+          FileSystem fs = managedFilesetPath.getFileSystem(configuration);
+          // Trigger lazy initialization
+          fs.exists(managedFilesetPath);
+        });
 
     // set oauth server path, but do not set other configs
     configuration.set(
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_OAUTH2_PATH_KEY, 
normal_path);
     assertThrows(
-        IllegalArgumentException.class, () -> 
managedFilesetPath.getFileSystem(configuration));
+        IllegalArgumentException.class,
+        () -> {
+          FileSystem fs = managedFilesetPath.getFileSystem(configuration);
+          // Trigger lazy initialization
+          fs.exists(managedFilesetPath);
+        });
 
     // set oauth credential, but do not set other configs
     configuration.set(
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_OAUTH2_CREDENTIAL_KEY,
         credential);
     assertThrows(
-        IllegalArgumentException.class, () -> 
managedFilesetPath.getFileSystem(configuration));
+        IllegalArgumentException.class,
+        () -> {
+          FileSystem fs = managedFilesetPath.getFileSystem(configuration);
+          // Trigger lazy initialization
+          fs.exists(managedFilesetPath);
+        });
 
     // set oauth scope, all configs are set
     configuration.set(
@@ -231,7 +252,12 @@ public class TestOauth2Client extends TestGvfsBase {
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_OAUTH2_PATH_KEY, 
invalid_path);
     // should throw UnauthorizedException
     assertThrows(
-        UnauthorizedException.class, () -> 
managedFilesetPath.getFileSystem(configuration));
+        UnauthorizedException.class,
+        () -> {
+          FileSystem fs = managedFilesetPath.getFileSystem(configuration);
+          // Trigger lazy initialization
+          fs.exists(managedFilesetPath);
+        });
 
     // 2. test wrong client secret
     mockResponse = 
response().withStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
@@ -255,7 +281,12 @@ public class TestOauth2Client extends TestGvfsBase {
               Times.exactly(1))
           .respond(mockResponse);
       assertThrows(
-          UnauthorizedException.class, () -> 
managedFilesetPath.getFileSystem(configuration));
+          UnauthorizedException.class,
+          () -> {
+            FileSystem fs = managedFilesetPath.getFileSystem(configuration);
+            // Trigger lazy initialization
+            fs.exists(managedFilesetPath);
+          });
     } catch (JsonProcessingException e) {
       throw new RuntimeException(e);
     }
@@ -333,7 +364,13 @@ public class TestOauth2Client extends TestGvfsBase {
     config1.set(
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_METALAKE_KEY, 
testMetalake);
     // UnauthorizedException will be caught by the client, and the 
RESTException will be thrown
-    assertThrows(RESTException.class, () -> newPath.getFileSystem(config1));
+    assertThrows(
+        RESTException.class,
+        () -> {
+          FileSystem fs = newPath.getFileSystem(config1);
+          // Trigger lazy initialization
+          fs.exists(newPath);
+        });
   }
 
   @Test
@@ -358,6 +395,12 @@ public class TestOauth2Client extends TestGvfsBase {
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_OAUTH2_PATH_KEY,
         invalid_path + "/bad");
     // should throw BadRequestException
-    assertThrows(BadRequestException.class, () -> 
managedFilesetPath.getFileSystem(configuration));
+    assertThrows(
+        BadRequestException.class,
+        () -> {
+          FileSystem fs = managedFilesetPath.getFileSystem(configuration);
+          // Trigger lazy initialization
+          fs.exists(managedFilesetPath);
+        });
   }
 }
diff --git 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestSimpleClient.java
 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestSimpleClient.java
index b88fbba16b..f80ef2bcb7 100644
--- 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestSimpleClient.java
+++ 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestSimpleClient.java
@@ -19,6 +19,7 @@
 package org.apache.gravitino.filesystem.hadoop;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -31,8 +32,10 @@ import org.apache.gravitino.auth.AuthConstants;
 import org.apache.gravitino.dto.AuditDTO;
 import org.apache.gravitino.dto.MetalakeDTO;
 import org.apache.gravitino.dto.responses.MetalakeResponse;
+import org.apache.gravitino.exceptions.RESTException;
 import org.apache.gravitino.json.JsonUtils;
 import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.Method;
@@ -98,7 +101,10 @@ public class TestSimpleClient extends TestGvfsBase {
     Configuration config1 = new Configuration(conf);
     config1.set(
         
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_METALAKE_KEY, 
testMetalake);
-    newPath.getFileSystem(config1);
+    FileSystem fs = newPath.getFileSystem(config1);
+    // Trigger lazy initialization to set auth token (throws RESTException for 
non-existent
+    // metalake)
+    assertThrows(RESTException.class, () -> fs.exists(newPath));
 
     String userInformation = user + ":dummy";
     assertEquals(


Reply via email to