This is an automated email from the ASF dual-hosted git repository.
kirs pushed a commit to branch branch-3.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-3.1 by this push:
new 1cdab3de053 branch-3.1: [chore](paimon)Unify object storage access via
HDFS protocol #54776 (#54979)
1cdab3de053 is described below
commit 1cdab3de053846e95b9216eee08f41c7742f90db
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Aug 19 17:50:45 2025 +0800
branch-3.1: [chore](paimon)Unify object storage access via HDFS protocol
#54776 (#54979)
Cherry-picked from #54776
Co-authored-by: Calvin Kirs <[email protected]>
---
.../docker-compose/hive/hadoop-hive-3x.env.tpl | 2 +
.../docker-compose/kerberos/hadoop-hive.env.tpl | 2 +
.../metastore/AbstractPaimonProperties.java | 31 -------
.../metastore/HMSGlueMetaStoreProperties.java | 4 +
.../storage/AbstractS3CompatibleProperties.java | 1 +
.../datasource/property/storage/COSProperties.java | 4 +-
.../datasource/property/storage/OSSProperties.java | 43 ++++++----
.../property/metastore/PaimonCatalogTest.java | 95 ++++++++++++++++++++++
8 files changed, 135 insertions(+), 47 deletions(-)
diff --git a/docker/thirdparties/docker-compose/hive/hadoop-hive-3x.env.tpl
b/docker/thirdparties/docker-compose/hive/hadoop-hive-3x.env.tpl
index c8afe5046d0..4675fbf8053 100644
--- a/docker/thirdparties/docker-compose/hive/hadoop-hive-3x.env.tpl
+++ b/docker/thirdparties/docker-compose/hive/hadoop-hive-3x.env.tpl
@@ -20,6 +20,7 @@
HIVE_SITE_CONF_hive_metastore_event_db_notification_api_auth=false
HIVE_SITE_CONF_hive_metastore_dml_events=true
HIVE_SITE_CONF_hive_metastore_transactional_event_listeners=org.apache.hive.hcatalog.listener.DbNotificationListener
HIVE_SITE_CONF_hive_stats_column_autogather=false
+HIVE_SITE_CONF_fs_s3_impl=org.apache.hadoop.fs.s3a.S3AFileSystem
HIVE_SITE_CONF_fs_s3a_impl=org.apache.hadoop.fs.s3a.S3AFileSystem
HIVE_SITE_CONF_fs_s3a_access_key=${AWSAk}
HIVE_SITE_CONF_fs_s3a_secret_key=${AWSSk}
@@ -33,6 +34,7 @@ HIVE_SITE_CONF_fs_cosn_userinfo_secretId=${COSAk}
HIVE_SITE_CONF_fs_cosn_userinfo_secretKey=${COSSk}
HIVE_SITE_CONF_fs_cosn_bucket_region=${COSRegion}
HIVE_SITE_CONF_fs_cosn_impl=org.apache.hadoop.fs.CosFileSystem
+HIVE_SITE_CONF_fs_cos_impl=org.apache.hadoop.fs.CosFileSystem
HIVE_SITE_CONF_fs_AbstractFileSystem_cosn_impl=org.apache.hadoop.fs.CosN
HIVE_SITE_CONF_fs_oss_impl=org.apache.hadoop.fs.aliyun.oss.AliyunOSSFileSystem
HIVE_SITE_CONF_fs_oss_accessKeyId=${OSSAk}
diff --git a/docker/thirdparties/docker-compose/kerberos/hadoop-hive.env.tpl
b/docker/thirdparties/docker-compose/kerberos/hadoop-hive.env.tpl
index f06c1ef7d7a..3c453ff0fbd 100644
--- a/docker/thirdparties/docker-compose/kerberos/hadoop-hive.env.tpl
+++ b/docker/thirdparties/docker-compose/kerberos/hadoop-hive.env.tpl
@@ -64,6 +64,7 @@
YARN_CONF_yarn_resourcemanager_webapp_address=${HOST}:${YARN_RM_WEBAPP_PORT}
YARN_CONF_yarn_nodemanager_localizer_address=${HOST}:${YARN_NM_LOCAL_PORT}
YARN_CONF_yarn_nodemanager_webapp_address=${HOST}:${YARN_NM_WEBAPP_PORT}
+HIVE_SITE_CONF_fs_s3_impl=org.apache.hadoop.fs.s3a.S3AFileSystem
HIVE_SITE_CONF_fs_s3a_impl=org.apache.hadoop.fs.s3a.S3AFileSystem
HIVE_SITE_CONF_fs_s3a_access_key=${AWSAk}
HIVE_SITE_CONF_fs_s3a_secret_key=${AWSSk}
@@ -77,6 +78,7 @@ HIVE_SITE_CONF_fs_cosn_userinfo_secretId=${COSAk}
HIVE_SITE_CONF_fs_cosn_userinfo_secretKey=${COSSk}
HIVE_SITE_CONF_fs_cosn_bucket_region=${COSRegion}
HIVE_SITE_CONF_fs_cosn_impl=org.apache.hadoop.fs.CosFileSystem
+HIVE_SITE_CONF_fs_cos_impl=org.apache.hadoop.fs.CosFileSystem
HIVE_SITE_CONF_fs_AbstractFileSystem_cosn_impl=org.apache.hadoop.fs.CosN
HIVE_SITE_CONF_fs_oss_impl=org.apache.hadoop.fs.aliyun.oss.AliyunOSSFileSystem
HIVE_SITE_CONF_fs_oss_accessKeyId=${OSSAk}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/AbstractPaimonProperties.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/AbstractPaimonProperties.java
index 7602758d2b0..040d04a1aad 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/AbstractPaimonProperties.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/AbstractPaimonProperties.java
@@ -19,7 +19,6 @@ package org.apache.doris.datasource.property.metastore;
import org.apache.doris.common.security.authentication.ExecutionAuthenticator;
import org.apache.doris.datasource.property.ConnectorProperty;
-import org.apache.doris.datasource.property.storage.S3Properties;
import org.apache.doris.datasource.property.storage.StorageProperties;
import lombok.Getter;
@@ -59,34 +58,6 @@ public abstract class AbstractPaimonProperties extends
MetastoreProperties {
public abstract Catalog initializeCatalog(String catalogName,
List<StorageProperties> storagePropertiesList);
- /**
- * Adapt S3 storage properties for Apache Paimon's S3 file system.
- *
- * <p>Paimon's S3 file system does not follow the standard
Hadoop-compatible
- * configuration keys (like fs.s3a.access.key). Instead, it expects
specific
- * keys such as "s3.access.key", "s3.secret.key", etc.
- *
- * <p>Therefore, we explicitly map our internal S3 configuration (usually
designed
- * for HDFS-compatible systems) to Paimon's expected format.
- *
- * <p>See: org.apache.paimon.s3.S3Loader
- *
- * @param storagePropertiesList the list of configured storage backends
- */
- protected void appendS3PropertiesIsNeeded(List<StorageProperties>
storagePropertiesList) {
-
- S3Properties s3Properties = (S3Properties)
storagePropertiesList.stream()
- .filter(storageProperties -> storageProperties.getType() ==
StorageProperties.Type.S3)
- .findFirst()
- .orElse(null);
- if (s3Properties != null) {
- catalogOptions.set("s3.access.key", s3Properties.getSecretKey());
- catalogOptions.set("s3.secret.key", s3Properties.getAccessKey());
- catalogOptions.set("s3.endpoint", s3Properties.getEndpoint());
- catalogOptions.set("s3.region", s3Properties.getRegion());
- }
- }
-
protected void appendCatalogOptions(List<StorageProperties>
storagePropertiesList) {
if (StringUtils.isNotBlank(warehouse)) {
catalogOptions.set(CatalogOptions.WAREHOUSE.key(), warehouse);
@@ -102,8 +73,6 @@ public abstract class AbstractPaimonProperties extends
MetastoreProperties {
}
}
});
-
- appendS3PropertiesIsNeeded(storagePropertiesList);
}
/**
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/HMSGlueMetaStoreProperties.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/HMSGlueMetaStoreProperties.java
index 477dba85ebf..1f31fe44dc2 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/HMSGlueMetaStoreProperties.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/metastore/HMSGlueMetaStoreProperties.java
@@ -41,6 +41,8 @@ public class HMSGlueMetaStoreProperties extends
AbstractHMSProperties {
public static final int DEFAULT_MAX_RETRY = 5;
public static final String AWS_GLUE_SOCKET_TIMEOUT_KEY =
"aws.glue.socket-timeout";
public static final int DEFAULT_SOCKET_TIMEOUT =
ClientConfiguration.DEFAULT_SOCKET_TIMEOUT;
+ public static final String
AWS_CATALOG_CREDENTIALS_PROVIDER_FACTORY_CLASS_KEY =
+ "aws.catalog.credentials.provider.factory.class";
// ========== Fields ==========
private AWSGlueMetaStoreBaseProperties baseProperties;
@@ -104,6 +106,8 @@ public class HMSGlueMetaStoreProperties extends
AbstractHMSProperties {
hiveConf.set(AWS_GLUE_CONNECTION_TIMEOUT_KEY,
String.valueOf(awsGlueConnectionTimeout));
hiveConf.set(AWS_GLUE_SOCKET_TIMEOUT_KEY,
String.valueOf(awsGlueSocketTimeout));
hiveConf.set(AWS_GLUE_CATALOG_SEPARATOR_KEY, awsGlueCatalogSeparator);
+ hiveConf.set(AWS_CATALOG_CREDENTIALS_PROVIDER_FACTORY_CLASS_KEY,
+
"com.amazonaws.glue.catalog.credentials.ConfigurationAWSCredentialsProviderFactory");
hiveConf.set("hive.metastore.type", "glue");
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractS3CompatibleProperties.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractS3CompatibleProperties.java
index 000bc75c529..509ebd81e87 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractS3CompatibleProperties.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractS3CompatibleProperties.java
@@ -303,6 +303,7 @@ public abstract class AbstractS3CompatibleProperties
extends StorageProperties i
hadoopStorageConfig.set("fs.s3.impl",
"org.apache.hadoop.fs.s3a.S3AFileSystem");
hadoopStorageConfig.set("fs.s3a.impl",
"org.apache.hadoop.fs.s3a.S3AFileSystem");
hadoopStorageConfig.set("fs.s3a.endpoint", getEndpoint());
+ hadoopStorageConfig.set("fs.s3a.endpoint.region", getRegion());
hadoopStorageConfig.set("fs.s3a.access.key", getAccessKey());
hadoopStorageConfig.set("fs.s3a.secret.key", getSecretKey());
hadoopStorageConfig.set("fs.s3a.connection.maximum",
getMaxConnections());
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/COSProperties.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/COSProperties.java
index 0e0f37e5566..f1820abb2b3 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/COSProperties.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/COSProperties.java
@@ -130,8 +130,8 @@ public class COSProperties extends
AbstractS3CompatibleProperties {
@Override
public void initializeHadoopStorageConfig() {
super.initializeHadoopStorageConfig();
- hadoopStorageConfig.set("fs.cos.impl",
"org.apache.hadoop.fs.CosFileSystem");
- hadoopStorageConfig.set("fs.cosn.impl",
"org.apache.hadoop.fs.CosFileSystem");
+ hadoopStorageConfig.set("fs.cos.impl",
"org.apache.hadoop.fs.s3a.S3AFileSystem");
+ hadoopStorageConfig.set("fs.cosn.impl",
"org.apache.hadoop.fs.s3a.S3AFileSystem");
hadoopStorageConfig.set("fs.cosn.bucket.region", region);
hadoopStorageConfig.set("fs.cosn.userinfo.secretId", accessKey);
hadoopStorageConfig.set("fs.cosn.userinfo.secretKey", secretKey);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OSSProperties.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OSSProperties.java
index 45208e6f3d3..07d56648498 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OSSProperties.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OSSProperties.java
@@ -97,6 +97,9 @@ public class OSSProperties extends
AbstractS3CompatibleProperties {
private static final List<String> URI_KEYWORDS = Arrays.asList("uri",
"warehouse");
+ private static List<String> DLF_TYPE_KEYWORDS =
Arrays.asList("hive.metastore.type",
+ "iceberg.catalog.type", "paimon.catalog.type");
+
protected OSSProperties(Map<String, String> origProps) {
super(Type.OSS, origProps);
}
@@ -128,7 +131,9 @@ public class OSSProperties extends
AbstractS3CompatibleProperties {
if (StringUtils.isNotBlank(value)) {
return true;
}
-
+ if (isDlfMSType(origProps)) {
+ return true;
+ }
Optional<String> uriValue = origProps.entrySet().stream()
.filter(e -> URI_KEYWORDS.stream()
.anyMatch(key -> key.equalsIgnoreCase(e.getKey())))
@@ -155,22 +160,32 @@ public class OSSProperties extends
AbstractS3CompatibleProperties {
return isAliyunOss || isAmazonS3 || isDls;
}
+ private static boolean isDlfMSType(Map<String, String> params) {
+ return DLF_TYPE_KEYWORDS.stream()
+ .anyMatch(key -> params.containsKey(key) &&
StringUtils.isNotBlank(params.get(key))
+ && StringUtils.equalsIgnoreCase("dlf",
params.get(key)));
+ }
+
@Override
protected void setEndpointIfPossible() {
if (StringUtils.isBlank(this.endpoint) &&
StringUtils.isNotBlank(this.region)) {
- Optional<String> uriValueOpt = origProps.entrySet().stream()
- .filter(e -> URI_KEYWORDS.stream()
- .anyMatch(key -> key.equalsIgnoreCase(e.getKey())))
- .map(Map.Entry::getValue)
- .filter(Objects::nonNull)
- .filter(OSSProperties::isKnownObjectStorage)
- .findFirst();
- if (uriValueOpt.isPresent()) {
- String uri = uriValueOpt.get();
- // If the URI does not start with http(s), derive endpoint
from region
- // (http(s) URIs are handled by separate logic elsewhere)
- if (!uri.startsWith("http://") && !uri.startsWith("https://"))
{
- this.endpoint = getOssEndpoint(region,
BooleanUtils.toBoolean(dlfAccessPublic));
+ if (isDlfMSType(origProps)) {
+ this.endpoint = getOssEndpoint(region,
BooleanUtils.toBoolean(dlfAccessPublic));
+ } else {
+ Optional<String> uriValueOpt = origProps.entrySet().stream()
+ .filter(e -> URI_KEYWORDS.stream()
+ .anyMatch(key ->
key.equalsIgnoreCase(e.getKey())))
+ .map(Map.Entry::getValue)
+ .filter(Objects::nonNull)
+ .filter(OSSProperties::isKnownObjectStorage)
+ .findFirst();
+ if (uriValueOpt.isPresent()) {
+ String uri = uriValueOpt.get();
+ // If the URI does not start with http(s), derive endpoint
from region
+ // (http(s) URIs are handled by separate logic elsewhere)
+ if (!uri.startsWith("http://") &&
!uri.startsWith("https://")) {
+ this.endpoint = getOssEndpoint(region,
BooleanUtils.toBoolean(dlfAccessPublic));
+ }
}
}
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/metastore/PaimonCatalogTest.java
b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/metastore/PaimonCatalogTest.java
new file mode 100644
index 00000000000..59766ef9902
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/metastore/PaimonCatalogTest.java
@@ -0,0 +1,95 @@
+// 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.doris.datasource.property.metastore;
+
+import org.apache.doris.datasource.property.storage.StorageProperties;
+
+import org.apache.paimon.catalog.Catalog;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Disabled("only used for your local test")
+public class PaimonCatalogTest {
+ @Test
+ public void testNameSpace() throws Exception {
+ Map<String, String> pa = new HashMap<>();
+ pa.put("type", "paimon");
+ pa.put("paimon.catalog.type", "hms");
+ pa.put("hive.metastore.uris", "thrift://172.20.48.119:9383");
+ pa.put("warehouse", "s3a://doris/paimon_warehouse");
+ pa.put("s3.region", "ap-east-1");
+
+ // User must provide real Access Key / Secret Key to enable
initialization
+ pa.put("s3.access_key", "");
+ pa.put("s3.secret_key", "");
+ pa.put("s3.endpoint", "s3.ap-east-1.amazonaws.com");
+
+ Catalog catalog = initCatalog(pa);
+ if (catalog != null) {
+ catalog.listDatabases().forEach(System.out::println);
+ }
+ }
+
+ /**
+ * Initializes a Paimon HMS Catalog.
+ * <p>
+ * Initialization is skipped by default. Users must provide valid S3
+ * Access Key and Secret Key in the configuration map to enable it.
+ * <p>
+ * Steps:
+ * 1. Validate that credentials are provided.
+ * 2. Normalize and check metastore properties.
+ * 3. Create storage properties.
+ * 4. Initialize and return the Catalog instance.
+ *
+ * @param params A map containing the configuration parameters.
+ * @return Catalog instance if initialized, or {@code null} if skipped.
+ * @throws Exception If initialization fails.
+ */
+ private Catalog initCatalog(Map<String, String> params) throws Exception {
+ if (isDisabled(params)) {
+ System.out.println("Catalog initialization skipped: Missing valid
S3 Access Key/Secret Key.");
+ return null;
+ }
+
+ AbstractPaimonProperties metaStoreProps =
+ (AbstractPaimonProperties) MetastoreProperties.create(params);
+
+ metaStoreProps.initNormalizeAndCheckProps();
+
+ List<StorageProperties> storageProps =
StorageProperties.createAll(params);
+
+ return metaStoreProps.initializeCatalog("paimon_catalog",
storageProps);
+ }
+
+ /**
+ * Checks if initialization should be skipped due to missing credentials.
+ *
+ * @param params The configuration parameters.
+ * @return {@code true} if missing AK/SK, {@code false} otherwise.
+ */
+ private boolean isDisabled(Map<String, String> params) {
+ String ak = params.get("s3.access_key");
+ String sk = params.get("s3.secret_key");
+ return ak == null || ak.isEmpty() || sk == null || sk.isEmpty();
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]