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

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


The following commit(s) were added to refs/heads/master by this push:
     new f9cee3d81f [rest] Unify the REST User-Agent format (#10308)
f9cee3d81f is described below

commit f9cee3d81fb9621ab0f35d6c82565cadd53943a6
Author: Dapeng Sun(孙大鹏) <[email protected]>
AuthorDate: Fri Oct 2 09:42:00 2026 +0800

    [rest] Unify the REST User-Agent format (#10308)
---
 docs/generated/catalog_configuration.html          | 18 ++++++++
 paimon-api/pom.xml                                 | 13 ++++++
 .../org/apache/paimon/utils/BuildVersions.java     | 29 +++++++++++++
 .../org/apache/paimon/options/CatalogOptions.java  | 25 +++++++++++
 .../org/apache/paimon/rest/HttpClientUtils.java    | 35 ++++++++++++++++
 .../main/java/org/apache/paimon/rest/RESTApi.java  | 19 ++++++++-
 .../apache/paimon/rest/HttpClientUtilsTest.java    | 40 ++++++++++++++++++
 .../apache/paimon/rest/MockRESTCatalogTest.java    | 48 ++++++++++++++++++++++
 .../paimon/vfs/hadoop/PaimonVirtualFileSystem.java | 12 ++++--
 .../vfs/hadoop/MockRestVirtualFileSystemTest.java  | 39 +++++++++++++++++-
 10 files changed, 272 insertions(+), 6 deletions(-)

diff --git a/docs/generated/catalog_configuration.html 
b/docs/generated/catalog_configuration.html
index bc7226d1b0..443fce9910 100644
--- a/docs/generated/catalog_configuration.html
+++ b/docs/generated/catalog_configuration.html
@@ -206,6 +206,24 @@ under the License.
             <td>String</td>
             <td>Uri of metastore server.</td>
         </tr>
+        <tr>
+            <td><h5>user-agent.extended</h5></td>
+            <td style="word-wrap: break-word;">(none)</td>
+            <td>String</td>
+            <td>Free-form text appended to Paimon's unified User-Agent, sent 
on REST and object storage requests. For OSS, fs.oss.user.agent.extended takes 
precedence; for REST, header.User-Agent replaces the whole value.</td>
+        </tr>
+        <tr>
+            <td><h5>user-agent.features</h5></td>
+            <td style="word-wrap: break-word;">(none)</td>
+            <td>String</td>
+            <td>Space-separated features of Paimon's unified User-Agent, sent 
on REST and object storage requests. For OSS, fs.oss.user.agent.features takes 
precedence; for REST, header.User-Agent replaces the whole value.</td>
+        </tr>
+        <tr>
+            <td><h5>user-agent.module</h5></td>
+            <td style="word-wrap: break-word;">(none)</td>
+            <td>String</td>
+            <td>The module of Paimon's unified User-Agent, 
module(transport;features) extended, sent on REST and object storage requests. 
Defaults to the Paimon client and its version. For OSS, 
fs.oss.user.agent.module takes precedence; for REST, header.User-Agent replaces 
the whole value.</td>
+        </tr>
         <tr>
             <td><h5>warehouse</h5></td>
             <td style="word-wrap: break-word;">(none)</td>
diff --git a/paimon-api/pom.xml b/paimon-api/pom.xml
index 50314960ea..c3159a5612 100644
--- a/paimon-api/pom.xml
+++ b/paimon-api/pom.xml
@@ -71,6 +71,19 @@ under the License.
 
     <build>
         <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>templating-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>filter-sources</id>
+                        <goals>
+                            <goal>filter-sources</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
diff --git 
a/paimon-api/src/main/java-templates/org/apache/paimon/utils/BuildVersions.java 
b/paimon-api/src/main/java-templates/org/apache/paimon/utils/BuildVersions.java
new file mode 100644
index 0000000000..97d731a168
--- /dev/null
+++ 
b/paimon-api/src/main/java-templates/org/apache/paimon/utils/BuildVersions.java
@@ -0,0 +1,29 @@
+/*
+ * 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.paimon.utils;
+
+/** Versions written in at build time, so reading them needs no class loader 
lookup. */
+public final class BuildVersions {
+
+    public static final String PAIMON = "${project.version}";
+
+    public static final String HTTP_CLIENT = "${apache.hc.client.version}";
+
+    private BuildVersions() {}
+}
diff --git 
a/paimon-api/src/main/java/org/apache/paimon/options/CatalogOptions.java 
b/paimon-api/src/main/java/org/apache/paimon/options/CatalogOptions.java
index bda512a944..fc923cb854 100644
--- a/paimon-api/src/main/java/org/apache/paimon/options/CatalogOptions.java
+++ b/paimon-api/src/main/java/org/apache/paimon/options/CatalogOptions.java
@@ -208,6 +208,31 @@ public class CatalogOptions {
                                     + "Paimon can read and write to external 
storage paths, such as OSS or S3. "
                                     + "In order to access these external paths 
correctly, you also need to configure the corresponding access key and secret 
key.");
 
+    public static final ConfigOption<String> USER_AGENT_MODULE =
+            ConfigOptions.key("user-agent.module")
+                    .stringType()
+                    .noDefaultValue()
+                    .withDescription(
+                            "The module of Paimon's unified User-Agent, 
module(transport;features) extended, "
+                                    + "sent on REST and object storage 
requests. Defaults to the Paimon client and its version. "
+                                    + "For OSS, fs.oss.user.agent.module takes 
precedence; for REST, header.User-Agent replaces the whole value.");
+
+    public static final ConfigOption<String> USER_AGENT_FEATURES =
+            ConfigOptions.key("user-agent.features")
+                    .stringType()
+                    .noDefaultValue()
+                    .withDescription(
+                            "Space-separated features of Paimon's unified 
User-Agent, sent on REST and object storage requests. "
+                                    + "For OSS, fs.oss.user.agent.features 
takes precedence; for REST, header.User-Agent replaces the whole value.");
+
+    public static final ConfigOption<String> USER_AGENT_EXTENDED =
+            ConfigOptions.key("user-agent.extended")
+                    .stringType()
+                    .noDefaultValue()
+                    .withDescription(
+                            "Free-form text appended to Paimon's unified 
User-Agent, sent on REST and object storage requests. "
+                                    + "For OSS, fs.oss.user.agent.extended 
takes precedence; for REST, header.User-Agent replaces the whole value.");
+
     public static final ConfigOption<Boolean> FILE_IO_ALLOW_CACHE =
             ConfigOptions.key("file-io.allow-cache")
                     .booleanType()
diff --git 
a/paimon-api/src/main/java/org/apache/paimon/rest/HttpClientUtils.java 
b/paimon-api/src/main/java/org/apache/paimon/rest/HttpClientUtils.java
index 1444c8118d..620918dd89 100644
--- a/paimon-api/src/main/java/org/apache/paimon/rest/HttpClientUtils.java
+++ b/paimon-api/src/main/java/org/apache/paimon/rest/HttpClientUtils.java
@@ -18,9 +18,12 @@
 
 package org.apache.paimon.rest;
 
+import org.apache.paimon.options.Options;
 import org.apache.paimon.rest.interceptor.LoggingInterceptor;
 import org.apache.paimon.rest.interceptor.TimingInterceptor;
+import org.apache.paimon.utils.BuildVersions;
 import org.apache.paimon.utils.SensitiveConfigUtils;
+import org.apache.paimon.utils.StringUtils;
 
 import org.apache.hc.client5.http.classic.methods.HttpDelete;
 import org.apache.hc.client5.http.classic.methods.HttpGet;
@@ -55,6 +58,10 @@ import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import static org.apache.paimon.options.CatalogOptions.USER_AGENT_EXTENDED;
+import static org.apache.paimon.options.CatalogOptions.USER_AGENT_FEATURES;
+import static org.apache.paimon.options.CatalogOptions.USER_AGENT_MODULE;
+
 /** Utils for {@link HttpClientBuilder}. */
 public class HttpClientUtils {
 
@@ -80,12 +87,40 @@ public class HttpClientUtils {
     public static HttpClientBuilder createBuilder() {
         HttpClientBuilder clientBuilder = HttpClients.custom();
         clientBuilder.setDefaultRequestConfig(DEFAULT_REQUEST_CONFIG);
+        // A request's own User-Agent header, e.g. from header.User-Agent, 
still wins.
+        clientBuilder.setUserAgent(userAgent(new Options()));
 
         clientBuilder.setConnectionManager(configureConnectionManager());
         clientBuilder.setRetryStrategy(new 
ExponentialHttpRequestRetryStrategy(5));
         return clientBuilder;
     }
 
+    /**
+     * Paimon's unified User-Agent, {@code 
module(Apache-HttpClient/<version>;features) extended}.
+     */
+    public static String userAgent(Options options) {
+        String module = options.get(USER_AGENT_MODULE);
+        StringBuilder builder =
+                new StringBuilder(
+                                StringUtils.isNullOrWhitespaceOnly(module)
+                                        ? "Paimon/" + BuildVersions.PAIMON
+                                        : module.trim())
+                        .append("(Apache-HttpClient/")
+                        .append(BuildVersions.HTTP_CLIENT);
+        String features = options.get(USER_AGENT_FEATURES);
+        if (!StringUtils.isNullOrWhitespaceOnly(features)) {
+            for (String feature : features.trim().split("\\s+")) {
+                builder.append(';').append(feature);
+            }
+        }
+        builder.append(')');
+        String extended = options.get(USER_AGENT_EXTENDED);
+        if (!StringUtils.isNullOrWhitespaceOnly(extended)) {
+            builder.append(' ').append(extended.trim());
+        }
+        return builder.toString();
+    }
+
     private static HttpClientConnectionManager configureConnectionManager() {
         PoolingHttpClientConnectionManagerBuilder connectionManagerBuilder =
                 PoolingHttpClientConnectionManagerBuilder.create();
diff --git a/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java 
b/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
index 8246cb1533..77a871d400 100644
--- a/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
+++ b/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
@@ -129,6 +129,7 @@ import 
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.ObjectMap
 import javax.annotation.Nullable;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -174,6 +175,7 @@ import static 
org.apache.paimon.utils.Preconditions.checkNotNull;
 public class RESTApi {
 
     public static final String HEADER_PREFIX = "header.";
+    private static final String USER_AGENT_HEADER = "User-Agent";
     /**
      * Optional header carrying the URL-encoded {@link Identifier} JSON of the 
table which initiated
      * a dependency read.
@@ -240,15 +242,28 @@ public class RESTApi {
                                             ResourcePaths.config(),
                                             queryParams,
                                             ConfigResponse.class,
-                                            new RESTAuthFunction(baseHeaders, 
authProvider))
+                                            new RESTAuthFunction(
+                                                    withUserAgent(baseHeaders, 
options),
+                                                    authProvider))
                                     .merge(options.toMap()));
             baseHeaders.putAll(extractPrefixMap(options, HEADER_PREFIX));
         }
-        this.restAuthFunction = new RESTAuthFunction(baseHeaders, 
authProvider);
+        this.restAuthFunction =
+                new RESTAuthFunction(withUserAgent(baseHeaders, options), 
authProvider);
         this.options = options;
         this.resourcePaths = ResourcePaths.forCatalogProperties(options);
     }
 
+    /** Adds Paimon's unified User-Agent unless {@code header.User-Agent} is 
set. */
+    private static Map<String, String> withUserAgent(Map<String, String> 
headers, Options options) {
+        if 
(headers.keySet().stream().anyMatch(USER_AGENT_HEADER::equalsIgnoreCase)) {
+            return headers;
+        }
+        Map<String, String> result = new HashMap<>(headers);
+        result.put(USER_AGENT_HEADER, HttpClientUtils.userAgent(options));
+        return result;
+    }
+
     /** Get the configured options which has been merged from REST Server. */
     public Options options() {
         return options;
diff --git 
a/paimon-api/src/test/java/org/apache/paimon/rest/HttpClientUtilsTest.java 
b/paimon-api/src/test/java/org/apache/paimon/rest/HttpClientUtilsTest.java
index 5b1c3c2700..f8ae28fbfe 100644
--- a/paimon-api/src/test/java/org/apache/paimon/rest/HttpClientUtilsTest.java
+++ b/paimon-api/src/test/java/org/apache/paimon/rest/HttpClientUtilsTest.java
@@ -18,11 +18,15 @@
 
 package org.apache.paimon.rest;
 
+import org.apache.paimon.options.CatalogOptions;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.utils.BuildVersions;
 import org.apache.paimon.utils.SensitiveConfigUtils;
 
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
+import org.apache.hc.core5.util.VersionInfo;
 import org.assertj.core.api.ThrowableAssert;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -62,6 +66,42 @@ public class HttpClientUtilsTest {
         }
     }
 
+    @Test
+    public void testUserAgent() throws Exception {
+        AtomicReference<String> userAgent = new AtomicReference<>();
+        registerHandler(
+                "/ua",
+                exchange -> {
+                    
userAgent.set(exchange.getRequestHeaders().getFirst("User-Agent"));
+                    respond(exchange, 200, new byte[0]);
+                });
+
+        assertThat(HttpClientUtils.exists(url("/ua"))).isTrue();
+        assertThat(userAgent.get())
+                .isEqualTo(HttpClientUtils.userAgent(new Options()))
+                .isEqualTo(
+                        "Paimon/"
+                                + BuildVersions.PAIMON
+                                + "(Apache-HttpClient/"
+                                + BuildVersions.HTTP_CLIENT
+                                + ")");
+        Options options = new Options();
+        options.set(CatalogOptions.USER_AGENT_MODULE, "MyApp/1.0");
+        options.set(CatalogOptions.USER_AGENT_FEATURES, " Flink  Spark ");
+        options.set(CatalogOptions.USER_AGENT_EXTENDED, "vvr");
+        assertThat(HttpClientUtils.userAgent(options))
+                .isEqualTo(
+                        "MyApp/1.0(Apache-HttpClient/"
+                                + BuildVersions.HTTP_CLIENT
+                                + ";Flink;Spark) vvr");
+        assertThat(BuildVersions.PAIMON).matches("\\d+\\.\\d+\\S*");
+        assertThat(BuildVersions.HTTP_CLIENT)
+                .isEqualTo(
+                        VersionInfo.loadVersionInfo(
+                                        "org.apache.hc.client5", 
getClass().getClassLoader())
+                                .getRelease());
+    }
+
     @Test
     public void testExistsReturnsTrueForAvailableResource() throws Exception {
         registerHandler(
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java 
b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
index 0054af3d16..b878a198f7 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
@@ -68,6 +68,7 @@ import org.apache.paimon.table.sink.BatchTableCommit;
 import org.apache.paimon.types.DataField;
 import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.types.RowType;
+import org.apache.paimon.utils.BuildVersions;
 import org.apache.paimon.utils.InstantiationUtil;
 import org.apache.paimon.utils.JsonSerdeUtil;
 
@@ -1631,6 +1632,53 @@ class MockRESTCatalogTest extends RESTCatalogTest {
         checkHeader(customHeaderName, customHeaderValue);
     }
 
+    @Test
+    void testDefaultUserAgentInRequests() throws Exception {
+        restCatalogServer.clearReceivedHeaders();
+        initCatalog(false).listDatabases();
+
+        assertThat(restCatalogServer.getReceivedHeaders())
+                .isNotEmpty()
+                .allSatisfy(
+                        headers ->
+                                assertThat(headers)
+                                        .containsEntry(
+                                                "user-agent",
+                                                HttpClientUtils.userAgent(new 
Options())));
+    }
+
+    @Test
+    void testCatalogWideUserAgentOptionsInRequests() throws Exception {
+        options.set(CatalogOptions.USER_AGENT_FEATURES, "Flink");
+        options.set(CatalogOptions.USER_AGENT_EXTENDED, "vvr");
+        restCatalogServer.clearReceivedHeaders();
+        initCatalog(false).listDatabases();
+
+        String expected =
+                "Paimon/"
+                        + BuildVersions.PAIMON
+                        + "(Apache-HttpClient/"
+                        + BuildVersions.HTTP_CLIENT
+                        + ";Flink) vvr";
+        assertThat(restCatalogServer.getReceivedHeaders())
+                .isNotEmpty()
+                .allSatisfy(headers -> 
assertThat(headers).containsEntry("user-agent", expected));
+    }
+
+    @Test
+    void testConfiguredUserAgentWins() throws Exception {
+        options.set(RESTCatalogOptions.HTTP_USER_AGENT, "starrocks/user");
+        options.set(CatalogOptions.USER_AGENT_EXTENDED, "vvr");
+        restCatalogServer.clearReceivedHeaders();
+        initCatalog(false).listDatabases();
+
+        assertThat(restCatalogServer.getReceivedHeaders())
+                .isNotEmpty()
+                .allSatisfy(
+                        headers ->
+                                
assertThat(headers).containsEntry("user-agent", "starrocks/user"));
+    }
+
     @Test
     void testReadViaHeaderOnDependencyTableAndDataTokenRequests() throws 
Exception {
         Identifier root = Identifier.create("db", "root");
diff --git 
a/paimon-vfs/paimon-vfs-hadoop/src/main/java/org/apache/paimon/vfs/hadoop/PaimonVirtualFileSystem.java
 
b/paimon-vfs/paimon-vfs-hadoop/src/main/java/org/apache/paimon/vfs/hadoop/PaimonVirtualFileSystem.java
index 58d67e1f6d..4b20cb2f46 100644
--- 
a/paimon-vfs/paimon-vfs-hadoop/src/main/java/org/apache/paimon/vfs/hadoop/PaimonVirtualFileSystem.java
+++ 
b/paimon-vfs/paimon-vfs-hadoop/src/main/java/org/apache/paimon/vfs/hadoop/PaimonVirtualFileSystem.java
@@ -24,6 +24,7 @@ import org.apache.paimon.options.CatalogOptions;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.rest.RESTCatalogOptions;
 import org.apache.paimon.rest.responses.GetDatabaseResponse;
+import org.apache.paimon.utils.StringUtils;
 import org.apache.paimon.vfs.VFSCatalogIdentifier;
 import org.apache.paimon.vfs.VFSDatabaseIdentifier;
 import org.apache.paimon.vfs.VFSIdentifier;
@@ -58,7 +59,7 @@ public class PaimonVirtualFileSystem extends FileSystem {
     private VFSOperations vfsOperations;
     private Configuration conf;
 
-    private static final String USER_AGENT = "HadoopPVFS";
+    private static final String USER_AGENT_FEATURE = "HadoopPVFS";
     private static final long DEFAULT_BLOCK_SIZE = 128 * 1024 * 1024L;
 
     @Override
@@ -96,8 +97,13 @@ public class PaimonVirtualFileSystem extends FileSystem {
             options.set(RESTCatalogOptions.URI, endpoint);
         }
 
-        // Set user agent
-        options.set(RESTCatalogOptions.HTTP_USER_AGENT, USER_AGENT);
+        // PVFS leads the features of Paimon's unified User-Agent.
+        String features = options.get(CatalogOptions.USER_AGENT_FEATURES);
+        options.set(
+                CatalogOptions.USER_AGENT_FEATURES,
+                StringUtils.isNullOrWhitespaceOnly(features)
+                        ? USER_AGENT_FEATURE
+                        : USER_AGENT_FEATURE + " " + features.trim());
 
         vfsOperations = new VFSOperations(options);
     }
diff --git 
a/paimon-vfs/paimon-vfs-hadoop/src/test/java/org/apache/paimon/vfs/hadoop/MockRestVirtualFileSystemTest.java
 
b/paimon-vfs/paimon-vfs-hadoop/src/test/java/org/apache/paimon/vfs/hadoop/MockRestVirtualFileSystemTest.java
index 46f8bd17fe..e0b74ccd10 100644
--- 
a/paimon-vfs/paimon-vfs-hadoop/src/test/java/org/apache/paimon/vfs/hadoop/MockRestVirtualFileSystemTest.java
+++ 
b/paimon-vfs/paimon-vfs-hadoop/src/test/java/org/apache/paimon/vfs/hadoop/MockRestVirtualFileSystemTest.java
@@ -20,6 +20,8 @@ package org.apache.paimon.vfs.hadoop;
 
 import org.apache.paimon.catalog.CatalogContext;
 import org.apache.paimon.options.CatalogOptions;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.rest.HttpClientUtils;
 import org.apache.paimon.rest.RESTCatalog;
 import org.apache.paimon.rest.RESTCatalogInternalOptions;
 import org.apache.paimon.rest.RESTCatalogOptions;
@@ -35,15 +37,19 @@ import org.apache.paimon.rest.responses.ConfigResponse;
 import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableMap;
 
 import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.junit.Assert;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
 import java.util.Map;
 import java.util.UUID;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
 /** Test for {@link PaimonVirtualFileSystem} with Mock Rest Server. */
 public class MockRestVirtualFileSystemTest extends VirtualFileSystemTest {
     protected RESTCatalogServer restCatalogServer;
@@ -110,11 +116,42 @@ public class MockRestVirtualFileSystemTest extends 
VirtualFileSystemTest {
         return new RESTCatalog(CatalogContext.create(options));
     }
 
-    protected void initFs() throws Exception {
+    @Test
+    public void testUserAgent() throws Exception {
+        assertPvfsUserAgent(vfs, "HadoopPVFS");
+
+        Configuration conf = pvfsConf();
+        conf.set("fs.pvfs.user-agent.features", "Flink");
+        PaimonVirtualFileSystem withFeatures = new PaimonVirtualFileSystem();
+        withFeatures.initialize(vfsRoot.toUri(), conf);
+        assertPvfsUserAgent(withFeatures, "HadoopPVFS Flink");
+    }
+
+    private void assertPvfsUserAgent(FileSystem fs, String features) throws 
IOException {
+        restCatalogServer.clearReceivedHeaders();
+        fs.exists(new Path(vfsRoot, "test_db"));
+
+        Options expected = new Options();
+        expected.set(CatalogOptions.USER_AGENT_FEATURES, features);
+        assertThat(restCatalogServer.getReceivedHeaders())
+                .isNotEmpty()
+                .allSatisfy(
+                        headers ->
+                                assertThat(headers)
+                                        .containsEntry(
+                                                "user-agent", 
HttpClientUtils.userAgent(expected)));
+    }
+
+    private Configuration pvfsConf() {
         Configuration conf = new Configuration();
         conf.set("fs.pvfs.uri", restCatalogServer.getUrl());
         conf.set("fs.pvfs.token.provider", AuthProviderEnum.BEAR.identifier());
         conf.set("fs.pvfs.token", initToken);
+        return conf;
+    }
+
+    protected void initFs() throws Exception {
+        Configuration conf = pvfsConf();
         this.vfs = new PaimonVirtualFileSystem();
         this.vfsRoot = new Path("pvfs://" + restWarehouse + "/");
         this.vfs.initialize(vfsRoot.toUri(), conf);

Reply via email to