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

jshao 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 6bea0e9320 [#6942] feat(clients): clients and gvfs support config 
request headers (#6976)
6bea0e9320 is described below

commit 6bea0e9320ed34584fff639b377de1aa9bf36386
Author: mchades <[email protected]>
AuthorDate: Thu Apr 17 17:58:15 2025 +0800

    [#6942] feat(clients): clients and gvfs support config request headers 
(#6976)
    
    ### What changes were proposed in this pull request?
    
    clients and gvfs support config request headers
    
    ### Why are the changes needed?
    
    Fix: #6942
    
    ### Does this PR introduce _any_ user-facing change?
    
    no
    
    ### How was this patch tested?
    
    tests added
---
 .../gravitino/client/gravitino_client.py           |  4 +-
 .../gravitino/client/gravitino_client_base.py      |  5 ++-
 .../gravitino/filesystem/gvfs_base_operations.py   | 17 +++++++-
 .../gravitino/filesystem/gvfs_config.py            |  3 ++
 .../gravitino/filesystem/gvfs_utils.py             |  2 +
 .../tests/unittests/test_gravitino_client.py       | 45 +++++++++++++++++++++
 .../tests/unittests/test_gvfs_with_local.py        | 13 ++++++
 .../GravitinoVirtualFileSystemConfiguration.java   |  4 ++
 .../hadoop/GravitinoVirtualFileSystemUtils.java    | 14 +++++++
 .../filesystem/hadoop/GravitinoMockServerBase.java |  8 ++++
 .../gravitino/filesystem/hadoop/TestGvfsBase.java  | 47 ++++++++++++++++++++++
 docs/how-to-use-gvfs.md                            |  2 +
 12 files changed, 161 insertions(+), 3 deletions(-)

diff --git a/clients/client-python/gravitino/client/gravitino_client.py 
b/clients/client-python/gravitino/client/gravitino_client.py
index e182f5a200..a8c3cd8535 100644
--- a/clients/client-python/gravitino/client/gravitino_client.py
+++ b/clients/client-python/gravitino/client/gravitino_client.py
@@ -39,6 +39,7 @@ class GravitinoClient(GravitinoClientBase):
         metalake_name: str,
         check_version: bool = True,
         auth_data_provider: AuthDataProvider = None,
+        request_headers: dict = None,
     ):
         """Constructs a new GravitinoClient with the given URI, authenticator 
and AuthDataProvider.
 
@@ -46,11 +47,12 @@ class GravitinoClient(GravitinoClientBase):
             uri: The base URI for the Gravitino API.
             metalake_name: The specified metalake name.
             auth_data_provider: The provider of the data which is used for 
authentication.
+            request_headers: The headers to be included in the HTTP requests.
 
         Raises:
             NoSuchMetalakeException if the metalake with specified name does 
not exist.
         """
-        super().__init__(uri, check_version, auth_data_provider)
+        super().__init__(uri, check_version, auth_data_provider, 
request_headers)
         self.check_metalake_name(metalake_name)
         self._metalake = super().load_metalake(metalake_name)
 
diff --git a/clients/client-python/gravitino/client/gravitino_client_base.py 
b/clients/client-python/gravitino/client/gravitino_client_base.py
index b61637f6a0..7a1744595f 100644
--- a/clients/client-python/gravitino/client/gravitino_client_base.py
+++ b/clients/client-python/gravitino/client/gravitino_client_base.py
@@ -56,8 +56,11 @@ class GravitinoClientBase:
         uri: str,
         check_version: bool = True,
         auth_data_provider: AuthDataProvider = None,
+        request_headers: dict = None,
     ):
-        self._rest_client = HTTPClient(uri, 
auth_data_provider=auth_data_provider)
+        self._rest_client = HTTPClient(
+            uri, auth_data_provider=auth_data_provider, 
request_headers=request_headers
+        )
         if check_version:
             self.check_version()
 
diff --git a/clients/client-python/gravitino/filesystem/gvfs_base_operations.py 
b/clients/client-python/gravitino/filesystem/gvfs_base_operations.py
index b847cccf59..62cc40934c 100644
--- a/clients/client-python/gravitino/filesystem/gvfs_base_operations.py
+++ b/clients/client-python/gravitino/filesystem/gvfs_base_operations.py
@@ -86,7 +86,22 @@ class BaseGVFSOperations(ABC):
         self._metalake = metalake_name
         self._options = options
 
-        self._client = create_client(options, server_uri, metalake_name)
+        request_headers = (
+            None
+            if options is None
+            else {
+                key[
+                    
len(GVFSConfig.GVFS_FILESYSTEM_CLIENT_REQUEST_HEADER_PREFIX) :
+                ]: value
+                for key, value in options.items()
+                if key.startswith(
+                    GVFSConfig.GVFS_FILESYSTEM_CLIENT_REQUEST_HEADER_PREFIX
+                )
+            }
+        )
+        self._client = create_client(
+            options, server_uri, metalake_name, request_headers
+        )
 
         cache_size = (
             GVFSConfig.DEFAULT_CACHE_SIZE
diff --git a/clients/client-python/gravitino/filesystem/gvfs_config.py 
b/clients/client-python/gravitino/filesystem/gvfs_config.py
index 47aa55b763..159b5ab986 100644
--- a/clients/client-python/gravitino/filesystem/gvfs_config.py
+++ b/clients/client-python/gravitino/filesystem/gvfs_config.py
@@ -67,3 +67,6 @@ class GVFSConfig:
 
     # The hook class that will be used to intercept file system operations.
     GVFS_FILESYSTEM_HOOK = "hook_class"
+
+    # The configuration key prefix for the client request headers.
+    GVFS_FILESYSTEM_CLIENT_REQUEST_HEADER_PREFIX = "client_request_header_"
diff --git a/clients/client-python/gravitino/filesystem/gvfs_utils.py 
b/clients/client-python/gravitino/filesystem/gvfs_utils.py
index b3b7f8f385..bb4553288a 100644
--- a/clients/client-python/gravitino/filesystem/gvfs_utils.py
+++ b/clients/client-python/gravitino/filesystem/gvfs_utils.py
@@ -67,6 +67,7 @@ def create_client(
             uri=server_uri,
             metalake_name=metalake_name,
             auth_data_provider=SimpleAuthProvider(),
+            request_headers=request_headers,
         )
 
     if auth_type == GVFSConfig.OAUTH2_AUTH_TYPE:
@@ -90,6 +91,7 @@ def create_client(
             uri=server_uri,
             metalake_name=metalake_name,
             auth_data_provider=oauth2_token_provider,
+            request_headers=request_headers,
         )
 
     raise GravitinoRuntimeException(
diff --git a/clients/client-python/tests/unittests/test_gravitino_client.py 
b/clients/client-python/tests/unittests/test_gravitino_client.py
new file mode 100644
index 0000000000..ef8e87615a
--- /dev/null
+++ b/clients/client-python/tests/unittests/test_gravitino_client.py
@@ -0,0 +1,45 @@
+# 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.
+import unittest
+
+from gravitino import GravitinoAdminClient, GravitinoClient
+from tests.unittests import mock_base
+
+
+@mock_base.mock_data
+class TestMetalake(unittest.TestCase):
+    # pylint: disable=W0212
+    def test_gravitino_client_headers(self, *mock_methods):
+        expected_headers = {
+            "k1": "v1",
+        }
+        gravitino_admin_client = GravitinoAdminClient(
+            uri="http://localhost:8090";,
+            request_headers=expected_headers,
+        )
+        self.assertEqual(
+            expected_headers, 
gravitino_admin_client._rest_client.request_headers
+        )
+
+        gravitino_client = GravitinoClient(
+            uri="http://localhost:8090";,
+            metalake_name="test",
+            request_headers=expected_headers,
+        )
+        self.assertEqual(
+            expected_headers, gravitino_client._rest_client.request_headers
+        )
diff --git a/clients/client-python/tests/unittests/test_gvfs_with_local.py 
b/clients/client-python/tests/unittests/test_gvfs_with_local.py
index 8090f051ed..30b5c0f436 100644
--- a/clients/client-python/tests/unittests/test_gvfs_with_local.py
+++ b/clients/client-python/tests/unittests/test_gvfs_with_local.py
@@ -82,6 +82,19 @@ class TestLocalFilesystem(unittest.TestCase):
         if local_fs.exists(self._local_base_dir_path):
             local_fs.rm(self._local_base_dir_path, recursive=True)
 
+    def test_request_headers(self, *mock_methods):
+        options = {
+            f"{GVFSConfig.GVFS_FILESYSTEM_CLIENT_REQUEST_HEADER_PREFIX}k1": 
"v1",
+        }
+        fs = gvfs.GravitinoVirtualFileSystem(
+            server_uri="http://localhost:9090";,
+            metalake_name="metalake_demo",
+            options=options,
+            skip_instance_cache=True,
+        )
+        headers = fs._operations._client._rest_client.request_headers
+        self.assertEqual(headers["k1"], "v1")
+
     def test_cache(self, *mock_methods):
         fileset_storage_location = f"{self._fileset_dir}/test_cache"
         fileset_virtual_location = "fileset/fileset_catalog/tmp/test_cache"
diff --git 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemConfiguration.java
 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemConfiguration.java
index c79cd6e890..f541a3492c 100644
--- 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemConfiguration.java
+++ 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemConfiguration.java
@@ -135,5 +135,9 @@ public class GravitinoVirtualFileSystemConfiguration {
   /** The default value for the Gravitino hook class. */
   public static final String FS_GRAVITINO_HOOK_CLASS_DEFAULT = 
NoOpHook.class.getCanonicalName();
 
+  /** The configuration key prefix for the Gravitino client request header. */
+  public static final String FS_GRAVITINO_CLIENT_REQUEST_HEADER_PREFIX =
+      "fs.gravitino.client.request.header.";
+
   private GravitinoVirtualFileSystemConfiguration() {}
 }
diff --git 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemUtils.java
 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemUtils.java
index 181843cfd5..926637072b 100644
--- 
a/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemUtils.java
+++ 
b/clients/filesystem-hadoop3/src/main/java/org/apache/gravitino/filesystem/hadoop/GravitinoVirtualFileSystemUtils.java
@@ -19,12 +19,15 @@
 
 package org.apache.gravitino.filesystem.hadoop;
 
+import static 
org.apache.gravitino.filesystem.hadoop.GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_REQUEST_HEADER_PREFIX;
+
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Maps;
 import java.io.File;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.client.DefaultOAuth2TokenProvider;
@@ -83,6 +86,14 @@ public class GravitinoVirtualFileSystemUtils {
         "'%s' is not set in the configuration",
         GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_SERVER_URI_KEY);
 
+    Map<String, String> requestHeaders =
+        configuration.entrySet().stream()
+            .filter(e -> 
e.getKey().startsWith(FS_GRAVITINO_CLIENT_REQUEST_HEADER_PREFIX))
+            .collect(
+                Collectors.toMap(
+                    e -> 
e.getKey().substring(FS_GRAVITINO_CLIENT_REQUEST_HEADER_PREFIX.length()),
+                    Map.Entry::getValue));
+
     String authType =
         configuration.getOrDefault(
             
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_AUTH_TYPE_KEY,
@@ -91,6 +102,7 @@ public class GravitinoVirtualFileSystemUtils {
       return GravitinoClient.builder(serverUri)
           .withMetalake(metalakeValue)
           .withSimpleAuth()
+          .withHeaders(requestHeaders)
           .build();
     } else if (authType.equalsIgnoreCase(
         GravitinoVirtualFileSystemConfiguration.OAUTH2_AUTH_TYPE)) {
@@ -137,6 +149,7 @@ public class GravitinoVirtualFileSystemUtils {
       return GravitinoClient.builder(serverUri)
           .withMetalake(metalakeValue)
           .withOAuth(authDataProvider)
+          .withHeaders(requestHeaders)
           .build();
     } else if (authType.equalsIgnoreCase(
         GravitinoVirtualFileSystemConfiguration.KERBEROS_AUTH_TYPE)) {
@@ -167,6 +180,7 @@ public class GravitinoVirtualFileSystemUtils {
       return GravitinoClient.builder(serverUri)
           .withMetalake(metalakeValue)
           .withKerberosAuth(authDataProvider)
+          .withHeaders(requestHeaders)
           .build();
     } else {
       throw new IllegalArgumentException(
diff --git 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/GravitinoMockServerBase.java
 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/GravitinoMockServerBase.java
index ba5ca5a161..b950536d12 100644
--- 
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/GravitinoMockServerBase.java
+++ 
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/GravitinoMockServerBase.java
@@ -203,6 +203,14 @@ public abstract class GravitinoMockServerBase {
     }
   }
 
+  protected String getJsonString(Object obj) {
+    try {
+      return MAPPER.writeValueAsString(obj);
+    } catch (JsonProcessingException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
   public static ClientAndServer mockServer() {
     return mockServer;
   }
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 eb6c674961..d9084f90f3 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
@@ -21,6 +21,7 @@ package org.apache.gravitino.filesystem.hadoop;
 import static org.apache.gravitino.file.Fileset.LOCATION_NAME_UNKNOWN;
 import static org.apache.gravitino.file.Fileset.PROPERTY_DEFAULT_LOCATION_NAME;
 import static 
org.apache.gravitino.filesystem.hadoop.GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_BLOCK_SIZE_DEFAULT;
+import static 
org.apache.gravitino.filesystem.hadoop.GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_REQUEST_HEADER_PREFIX;
 import static 
org.apache.gravitino.filesystem.hadoop.GravitinoVirtualFileSystemUtils.extractIdentifier;
 import static org.apache.hc.core5.http.HttpStatus.SC_NOT_FOUND;
 import static org.apache.hc.core5.http.HttpStatus.SC_OK;
@@ -35,6 +36,8 @@ 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.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.collect.ImmutableMap;
@@ -52,6 +55,7 @@ import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Version;
 import org.apache.gravitino.dto.AuditDTO;
 import org.apache.gravitino.dto.credential.CredentialDTO;
 import org.apache.gravitino.dto.file.FilesetDTO;
@@ -59,6 +63,7 @@ import org.apache.gravitino.dto.responses.CredentialResponse;
 import org.apache.gravitino.dto.responses.ErrorResponse;
 import org.apache.gravitino.dto.responses.FileLocationResponse;
 import org.apache.gravitino.dto.responses.FilesetResponse;
+import org.apache.gravitino.dto.responses.VersionResponse;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchFilesetException;
 import org.apache.gravitino.exceptions.NoSuchLocationNameException;
@@ -80,6 +85,9 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
 import org.junit.jupiter.params.provider.ValueSource;
 import org.mockito.Mockito;
+import org.mockserver.matchers.Times;
+import org.mockserver.model.HttpRequest;
+import org.mockserver.verify.VerificationTimes;
 
 public class TestGvfsBase extends GravitinoMockServerBase {
   protected static final String GVFS_IMPL_CLASS = 
GravitinoVirtualFileSystem.class.getName();
@@ -238,6 +246,45 @@ public class TestGvfsBase extends GravitinoMockServerBase {
     }
   }
 
+  @Test
+  public void testRequestHeaders()
+      throws NoSuchFieldException, IllegalAccessException, IOException {
+    String envKey = "GRAVITINO_TEST_HEADER";
+    String envValue = "v1";
+    String headerKey1 = "k1";
+    // prepare the env variable
+    Map<String, String> env = System.getenv();
+    Field field = env.getClass().getDeclaredField("m");
+    field.setAccessible(true);
+    Map<String, String> writableEnv = (Map<String, String>) field.get(env);
+    writableEnv.put(envKey, envValue);
+
+    // test the request headers
+    String headerKey2 = "k2";
+    String headerValue2 = "v2";
+    Configuration configuration = new Configuration(conf);
+    configuration.set(
+        FS_GRAVITINO_CLIENT_REQUEST_HEADER_PREFIX + headerKey1, "${env." + 
envKey + "}");
+    configuration.set(FS_GRAVITINO_CLIENT_REQUEST_HEADER_PREFIX + headerKey2, 
headerValue2);
+
+    mockServer().clear(request().withPath("/api/version"));
+    HttpRequest req =
+        request()
+            .withHeader(headerKey1, envValue)
+            .withHeader(headerKey2, headerValue2)
+            .withPath("/api/version");
+    mockServer()
+        .when(req, Times.once())
+        .respond(
+            response()
+                .withStatusCode(SC_OK)
+                .withBody(getJsonString(new 
VersionResponse(Version.getCurrentVersionDTO()))));
+
+    try (FileSystem fs = new 
Path("gvfs://fileset/").getFileSystem(configuration)) {
+      mockServer().verify(req, VerificationTimes.once());
+    }
+  }
+
   @Test
   public void testFSCache() throws IOException {
     String filesetName = "testFSCache";
diff --git a/docs/how-to-use-gvfs.md b/docs/how-to-use-gvfs.md
index caaa2e92e8..996c61f947 100644
--- a/docs/how-to-use-gvfs.md
+++ b/docs/how-to-use-gvfs.md
@@ -69,6 +69,7 @@ the path mapping and convert automatically.
 | `fs.gravitino.current.location.name.env.var`          | The environment 
variable name to get the current location name.                                 
                                                                                
                                                                                
                                                     | `CURRENT_LOCATION_NAME`  
                                      | No                                  | 
0.9.0-incubating |
 | `fs.gravitino.operations.class`                       | The operations class 
to provide the FS operations for the Gravitino Virtual File System. Users can 
extends `BaseGVFSOperations` to implement their own operations and configure 
the class name in this conf to use custom FS operations.                        
                                                     | 
`org.apache.gravitino.filesystem.hadoop.DefaultGVFSOperations` | No             
                     | 0.9.0-incubating |
 | `fs.gravitino.hook.class`                             | The hook class to 
inject into the <br/>Gravitino Virtual File System. Users can implement their 
own `GravitinoVirtualFileSystemHook` and configure the class name in this conf 
to inject custom code.                                                          
                                                      | 
`org.apache.gravitino.filesystem.hadoop.NoOpHook`              | No             
                     | 0.9.0-incubating |
+| `fs.gravitino.client.request.header.`                 | The configuration 
key prefix for the Gravitino client request header. You can set the request 
header for the Gravitino client.                                                
                                                                                
                                                       | (none)                 
                                        | No                                  | 
0.9.0-incubating |
 
 Apart from the above properties, to access fileset like S3, GCS, OSS and 
custom fileset, extra properties are needed, please see 
 [S3 GVFS Java client 
configurations](./hadoop-catalog-with-s3.md#using-the-gvfs-java-client-to-access-the-fileset),
 [GCS GVFS Java client 
configurations](./hadoop-catalog-with-gcs.md#using-the-gvfs-java-client-to-access-the-fileset),
 [OSS GVFS Java client 
configurations](./hadoop-catalog-with-oss.md#using-the-gvfs-java-client-to-access-the-fileset)
 and [Azure Blob Storage GVFS Java client 
configurations](./hadoop-catalog-with-adls.md#using-the-gvfs-java-client-to-access-the-fileset)
 for  [...]
@@ -363,6 +364,7 @@ to recompile the native libraries like `libhdfs` and 
others, and completely repl
 | `current_location_name_env_var` | The environment variable name to get the 
current location name.                                                          
                                                                                
                                                                                
                    | `CURRENT_LOCATION_NAME`                                   
           | No                                | 0.9.0-incubating |
 | `operations_class`              | The operations class to provide the FS 
operations for the Gravitino Virtual File System. Users can extends 
`BaseGVFSOperations` to implement their own operations and configure the class 
name in this conf to use custom FS operations.                                  
                                   | 
`gravitino.filesystem.gvfs_default_operations.DefaultGVFSOperations` | No       
                         | 0.9.0-incubating |
 | `hook_class`                    | The hook class to inject into the 
Gravitino Virtual File System. Users can implement their own 
`GravitinoVirtualFileSystemHook` and configure the class name in this conf to 
inject custom code.                                                             
                                                | 
`gravitino.filesystem.gvfs_hook.NoOpHook`                            | No       
                         | 0.9.0-incubating |
+| `client_request_header_`        | The configuration key prefix for the 
Gravitino client request header. You can set the request header for the 
Gravitino client.                                                               
                                                                                
                                | (none)                                        
                       | No                                | 0.9.0-incubating |
 
 #### Configurations for S3, GCS, OSS and Azure Blob storage fileset
 

Reply via email to