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

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


The following commit(s) were added to refs/heads/master by this push:
     new 44babc37983 [opt](load) S3 Load and TVF support access without AKSK 
(#53592)
44babc37983 is described below

commit 44babc3798332888f68d1b1d6d46b996c6575d7f
Author: Xin Liao <[email protected]>
AuthorDate: Thu Jul 24 22:08:30 2025 +0800

    [opt](load) S3 Load and TVF support access without AKSK (#53592)
---
 be/src/util/s3_util.cpp                            |  19 +++-
 .../java/org/apache/doris/analysis/LoadStmt.java   |   7 --
 .../datasource/property/storage/COSProperties.java |  34 +++++++
 .../property/storage/MinioProperties.java          |  20 ++++
 .../datasource/property/storage/OBSProperties.java |  34 +++++++
 .../datasource/property/storage/OSSProperties.java |  30 ++++++
 .../datasource/property/storage/S3Properties.java  |  15 ++-
 .../nereids/trees/plans/commands/LoadCommand.java  |   7 --
 .../property/storage/COSPropertiesTest.java        |  16 +++
 .../property/storage/MinioPropertiesTest.java      |  15 ++-
 .../property/storage/OBSPropertyTest.java          |  17 ++++
 .../property/storage/OSSPropertiesTest.java        |  18 ++++
 .../broker_load/test_s3_load_without_aksk.out      | Bin 0 -> 611 bytes
 .../data/load_p0/tvf/test_tvf_without_aksk.out     | Bin 0 -> 611 bytes
 .../test_s3_tvf_s3_storage.groovy                  |   6 +-
 ...t_domain_connection_and_ak_sk_correction.groovy |  55 ----------
 .../broker_load/test_s3_load_without_aksk.groovy   | 111 +++++++++++++++++++++
 .../load_p0/tvf/test_tvf_without_aksk.groovy       |  60 +++++++++++
 18 files changed, 383 insertions(+), 81 deletions(-)

diff --git a/be/src/util/s3_util.cpp b/be/src/util/s3_util.cpp
index 10b585ab045..f8643a3c031 100644
--- a/be/src/util/s3_util.cpp
+++ b/be/src/util/s3_util.cpp
@@ -82,12 +82,17 @@ doris::Status is_s3_conf_valid(const S3ClientConf& conf) {
     }
 
     if (conf.role_arn.empty()) {
-        if (conf.ak.empty()) {
-            return Status::InvalidArgument<false>("Invalid s3 conf, empty ak");
-        }
-        if (conf.sk.empty()) {
+        // Allow anonymous access when both ak and sk are empty
+        bool hasAk = !conf.ak.empty();
+        bool hasSk = !conf.sk.empty();
+
+        // Either both credentials are provided or both are empty (anonymous 
access)
+        if (hasAk && conf.sk.empty()) {
             return Status::InvalidArgument<false>("Invalid s3 conf, empty sk");
         }
+        if (hasSk && conf.ak.empty()) {
+            return Status::InvalidArgument<false>("Invalid s3 conf, empty ak");
+        }
     }
     return Status::OK();
 }
@@ -282,6 +287,12 @@ std::shared_ptr<Aws::Auth::AWSCredentialsProvider> 
S3ClientFactory::get_aws_cred
                 s3_conf.role_arn, Aws::String(), s3_conf.external_id,
                 Aws::Auth::DEFAULT_CREDS_LOAD_FREQ_SECONDS, stsClient);
     }
+
+    // Support anonymous access for public datasets when no credentials are 
provided
+    if (s3_conf.ak.empty() && s3_conf.sk.empty()) {
+        return std::make_shared<Aws::Auth::AnonymousAWSCredentialsProvider>();
+    }
+
     return std::make_shared<Aws::Auth::DefaultAWSCredentialsProviderChain>();
 }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/LoadStmt.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/LoadStmt.java
index 3c00ea46c2b..0ea39c56a85 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/LoadStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/LoadStmt.java
@@ -27,7 +27,6 @@ import org.apache.doris.common.UserException;
 import org.apache.doris.common.util.PrintableMap;
 import org.apache.doris.common.util.TimeUtils;
 import org.apache.doris.datasource.property.storage.ObjectStorageProperties;
-import org.apache.doris.fs.FileSystemFactory;
 import org.apache.doris.load.EtlJobType;
 import org.apache.doris.load.loadv2.LoadTask;
 import org.apache.doris.mysql.privilege.PrivPredicate;
@@ -627,12 +626,6 @@ public class LoadStmt extends DdlStmt implements 
NotFallbackInParser {
                     }
                 }
             }
-            //should add connectivity test
-            boolean connectivityTest = 
FileSystemFactory.get(brokerDesc.getStorageProperties())
-                    .connectivityTest(filePaths);
-            if (!connectivityTest) {
-                throw new UserException("Failed to access object storage, 
message=connectivity test failed");
-            }
         }
     }
 
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 b6e4436fdb1..4bb9d0fc3b6 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
@@ -18,11 +18,15 @@
 package org.apache.doris.datasource.property.storage;
 
 import org.apache.doris.datasource.property.ConnectorProperty;
+import 
org.apache.doris.datasource.property.storage.exception.StoragePropertiesException;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
+import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
 
 import java.util.Map;
 import java.util.Objects;
@@ -49,11 +53,13 @@ public class COSProperties extends 
AbstractS3CompatibleProperties {
 
     @Getter
     @ConnectorProperty(names = {"cos.access_key", "s3.access_key", 
"AWS_ACCESS_KEY", "access_key", "ACCESS_KEY"},
+            required = false,
             description = "The access key of COS.")
     protected String accessKey = "";
 
     @Getter
     @ConnectorProperty(names = {"cos.secret_key", "s3.secret_key", 
"AWS_SECRET_KEY", "secret_key", "SECRET_KEY"},
+            required = false,
             description = "The secret key of COS.")
     protected String secretKey = "";
 
@@ -71,6 +77,22 @@ public class COSProperties extends 
AbstractS3CompatibleProperties {
         super(Type.COS, origProps);
     }
 
+    @Override
+    public void initNormalizeAndCheckProps() {
+        super.initNormalizeAndCheckProps();
+        // Check if credentials are provided properly - either both or neither
+        if (StringUtils.isNotBlank(accessKey) && 
StringUtils.isNotBlank(secretKey)) {
+            return;
+        }
+        // Allow anonymous access if both access_key and secret_key are empty
+        if (StringUtils.isBlank(accessKey) && StringUtils.isBlank(secretKey)) {
+            return;
+        }
+        // If only one is provided, it's an error
+        throw new StoragePropertiesException(
+                "Please set access_key and secret_key or omit both for 
anonymous access to public bucket.");
+    }
+
     protected static boolean guessIsMe(Map<String, String> origProps) {
         String value = Stream.of("cos.endpoint", "s3.endpoint", 
"AWS_ENDPOINT", "endpoint", "ENDPOINT")
                 .map(origProps::get)
@@ -92,4 +114,16 @@ public class COSProperties extends 
AbstractS3CompatibleProperties {
         return ENDPOINT_PATTERN;
     }
 
+    @Override
+    public AwsCredentialsProvider getAwsCredentialsProvider() {
+        AwsCredentialsProvider credentialsProvider = 
super.getAwsCredentialsProvider();
+        if (credentialsProvider != null) {
+            return credentialsProvider;
+        }
+        if (StringUtils.isBlank(accessKey) && StringUtils.isBlank(secretKey)) {
+            // For anonymous access (no credentials required)
+            return AnonymousCredentialsProvider.create();
+        }
+        return null;
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/MinioProperties.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/MinioProperties.java
index 604191070cc..6da7ce4f231 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/MinioProperties.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/MinioProperties.java
@@ -18,10 +18,12 @@
 package org.apache.doris.datasource.property.storage;
 
 import org.apache.doris.datasource.property.ConnectorProperty;
+import 
org.apache.doris.datasource.property.storage.exception.StoragePropertiesException;
 
 import com.google.common.collect.ImmutableSet;
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
 
 import java.util.Map;
 import java.util.Set;
@@ -40,11 +42,13 @@ public class MinioProperties extends 
AbstractS3CompatibleProperties {
 
     @Getter
     @ConnectorProperty(names = {"minio.access_key", "AWS_ACCESS_KEY", 
"ACCESS_KEY", "access_key", "s3.access_key"},
+            required = false,
             description = "The access key of Minio.")
     protected String accessKey = "";
 
     @Getter
     @ConnectorProperty(names = {"minio.secret_key", "s3.secret_key", 
"AWS_SECRET_KEY", "secret_key", "SECRET_KEY"},
+            required = false,
             description = "The secret key of Minio.")
     protected String secretKey = "";
 
@@ -60,6 +64,22 @@ public class MinioProperties extends 
AbstractS3CompatibleProperties {
         super(Type.MINIO, origProps);
     }
 
+    @Override
+    public void initNormalizeAndCheckProps() {
+        super.initNormalizeAndCheckProps();
+        // Check if credentials are provided properly - either both or neither
+        if (StringUtils.isNotBlank(accessKey) && 
StringUtils.isNotBlank(secretKey)) {
+            return;
+        }
+        // Allow anonymous access if both access_key and secret_key are empty
+        if (StringUtils.isBlank(accessKey) && StringUtils.isBlank(secretKey)) {
+            return;
+        }
+        // If only one is provided, it's an error
+        throw new StoragePropertiesException(
+                        "Please set access_key and secret_key or omit both for 
anonymous access to public bucket.");
+    }
+
     public static boolean guessIsMe(Map<String, String> origProps) {
         //ugly, but we need to check if the user has set any of the identifiers
         if (AzureProperties.guessIsMe(origProps) || 
COSProperties.guessIsMe(origProps)
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OBSProperties.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OBSProperties.java
index 1eafa36d237..5acd7d8c5da 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OBSProperties.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/OBSProperties.java
@@ -18,11 +18,15 @@
 package org.apache.doris.datasource.property.storage;
 
 import org.apache.doris.datasource.property.ConnectorProperty;
+import 
org.apache.doris.datasource.property.storage.exception.StoragePropertiesException;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
+import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
 
 import java.util.Map;
 import java.util.Objects;
@@ -42,11 +46,13 @@ public class OBSProperties extends 
AbstractS3CompatibleProperties {
 
     @Getter
     @ConnectorProperty(names = {"obs.access_key", "s3.access_key", 
"AWS_ACCESS_KEY", "access_key", "ACCESS_KEY"},
+            required = false,
             description = "The access key of OBS.")
     protected String accessKey = "";
 
     @Getter
     @ConnectorProperty(names = {"obs.secret_key", "s3.secret_key", 
"AWS_SECRET_KEY", "secret_key", "SECRET_KEY"},
+            required = false,
             description = "The secret key of OBS.")
     protected String secretKey = "";
 
@@ -75,6 +81,22 @@ public class OBSProperties extends 
AbstractS3CompatibleProperties {
         // Initialize fields from origProps
     }
 
+    @Override
+    public void initNormalizeAndCheckProps() {
+        super.initNormalizeAndCheckProps();
+        // Check if credentials are provided properly - either both or neither
+        if (StringUtils.isNotBlank(accessKey) && 
StringUtils.isNotBlank(secretKey)) {
+            return;
+        }
+        // Allow anonymous access if both access_key and secret_key are empty
+        if (StringUtils.isBlank(accessKey) && StringUtils.isBlank(secretKey)) {
+            return;
+        }
+        // If only one is provided, it's an error
+        throw new StoragePropertiesException(
+                        "Please set access_key and secret_key or omit both for 
anonymous access to public bucket.");
+    }
+
     protected static boolean guessIsMe(Map<String, String> origProps) {
         String value = Stream.of("obs.endpoint", "s3.endpoint", 
"AWS_ENDPOINT", "endpoint", "ENDPOINT")
                 .map(origProps::get)
@@ -97,4 +119,16 @@ public class OBSProperties extends 
AbstractS3CompatibleProperties {
         return ENDPOINT_PATTERN;
     }
 
+    @Override
+    public AwsCredentialsProvider getAwsCredentialsProvider() {
+        AwsCredentialsProvider credentialsProvider = 
super.getAwsCredentialsProvider();
+        if (credentialsProvider != null) {
+            return credentialsProvider;
+        }
+        if (StringUtils.isBlank(accessKey) && StringUtils.isBlank(secretKey)) {
+            // For anonymous access (no credentials required)
+            return AnonymousCredentialsProvider.create();
+        }
+        return null;
+    }
 }
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 fe71c060cc0..f79480dea04 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
@@ -18,11 +18,15 @@
 package org.apache.doris.datasource.property.storage;
 
 import org.apache.doris.datasource.property.ConnectorProperty;
+import 
org.apache.doris.datasource.property.storage.exception.StoragePropertiesException;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
+import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
 
 import java.util.Map;
 import java.util.Objects;
@@ -44,12 +48,14 @@ public class OSSProperties extends 
AbstractS3CompatibleProperties {
     @Getter
     @ConnectorProperty(names = {"oss.access_key", "s3.access_key", 
"AWS_ACCESS_KEY", "access_key", "ACCESS_KEY",
             "dlf.access_key", "dlf.catalog.accessKeyId"},
+            required = false,
             description = "The access key of OSS.")
     protected String accessKey = "";
 
     @Getter
     @ConnectorProperty(names = {"oss.secret_key", "s3.secret_key", 
"AWS_SECRET_KEY", "secret_key", "SECRET_KEY",
             "dlf.secret_key", "dlf.catalog.secret_key"},
+            required = false,
             description = "The secret key of OSS.")
     protected String secretKey = "";
 
@@ -122,6 +128,18 @@ public class OSSProperties extends 
AbstractS3CompatibleProperties {
             String publicAccess = 
origProps.getOrDefault("dlf.catalog.accessPublic", "false");
             this.endpoint = getOssEndpoint(region, 
Boolean.parseBoolean(publicAccess));
         }
+        // Check if credentials are provided properly - either both or neither
+        if (StringUtils.isNotBlank(accessKey) && 
StringUtils.isNotBlank(secretKey)) {
+            return;
+        }
+        // Allow anonymous access if both access_key and secret_key are empty
+        if (StringUtils.isBlank(accessKey) && StringUtils.isBlank(secretKey)) {
+            return;
+        }
+        // If only one is provided, it's an error
+        throw new StoragePropertiesException(
+                "Please set access_key and secret_key or omit both for 
anonymous access to public bucket.");
+
     }
 
     private static String getOssEndpoint(String region, boolean publicAccess) {
@@ -138,4 +156,16 @@ public class OSSProperties extends 
AbstractS3CompatibleProperties {
         return ENDPOINT_PATTERN;
     }
 
+    @Override
+    public AwsCredentialsProvider getAwsCredentialsProvider() {
+        AwsCredentialsProvider credentialsProvider = 
super.getAwsCredentialsProvider();
+        if (credentialsProvider != null) {
+            return credentialsProvider;
+        }
+        if (StringUtils.isBlank(accessKey) && StringUtils.isBlank(secretKey)) {
+            // For anonymous access (no credentials required)
+            return AnonymousCredentialsProvider.create();
+        }
+        return null;
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/S3Properties.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/S3Properties.java
index 91cc2e0b221..e53ca7f7e9f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/S3Properties.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/S3Properties.java
@@ -26,6 +26,7 @@ import com.google.common.collect.Lists;
 import lombok.Getter;
 import lombok.Setter;
 import org.apache.commons.lang3.StringUtils;
+import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
 import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
 import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain;
 import 
software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
@@ -71,14 +72,14 @@ public class S3Properties extends 
AbstractS3CompatibleProperties {
     @ConnectorProperty(names = {"s3.access_key", "AWS_ACCESS_KEY", 
"access_key", "ACCESS_KEY", "glue.access_key",
             "aws.glue.access-key", 
"client.credentials-provider.glue.access_key"},
             required = false,
-            description = "The access key of S3.")
+            description = "The access key of S3. Optional for anonymous access 
to public datasets.")
     protected String accessKey = "";
 
     @Getter
     @ConnectorProperty(names = {"s3.secret_key", "AWS_SECRET_KEY", 
"secret_key", "SECRET_KEY", "glue.secret_key",
             "aws.glue.secret-key", 
"client.credentials-provider.glue.secret_key"},
             required = false,
-            description = "The secret key of S3.")
+            description = "The secret key of S3. Optional for anonymous access 
to public datasets.")
     protected String secretKey = "";
 
 
@@ -170,8 +171,12 @@ public class S3Properties extends 
AbstractS3CompatibleProperties {
         if 
(Boolean.parseBoolean(origProps.getOrDefault("iceberg.rest.vended-credentials-enabled",
 "false"))) {
             return;
         }
+        // Allow anonymous access if both access_key and secret_key are empty
+        if (StringUtils.isBlank(accessKey) && StringUtils.isBlank(secretKey)) {
+            return;
+        }
         throw new StoragePropertiesException("Please set s3.access_key and 
s3.secret_key or s3.role_arn and "
-                + "s3.external_id");
+                + "s3.external_id or omit all for anonymous access to public 
bucket.");
     }
 
     /**
@@ -274,6 +279,10 @@ public class S3Properties extends 
AbstractS3CompatibleProperties {
                         }
                     }).build();
         }
+        // For anonymous access (no credentials required)
+        if (StringUtils.isBlank(accessKey) && StringUtils.isBlank(secretKey)) {
+            return AnonymousCredentialsProvider.create();
+        }
         return 
AwsCredentialsProviderChain.of(SystemPropertyCredentialsProvider.create(),
                 EnvironmentVariableCredentialsProvider.create(),
                 WebIdentityTokenFileCredentialsProvider.create(),
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/LoadCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/LoadCommand.java
index 73990394b90..eb73cc8ec4f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/LoadCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/LoadCommand.java
@@ -38,7 +38,6 @@ import org.apache.doris.common.InternalErrorCode;
 import org.apache.doris.common.UserException;
 import org.apache.doris.common.util.TimeUtils;
 import org.apache.doris.datasource.property.storage.ObjectStorageProperties;
-import org.apache.doris.fs.FileSystemFactory;
 import org.apache.doris.load.EtlJobType;
 import org.apache.doris.load.LoadJobRowResult;
 import org.apache.doris.load.loadv2.LoadManager;
@@ -493,12 +492,6 @@ public class LoadCommand extends Command implements 
NeedAuditEncryption, Forward
                     }
                 }
             }
-            //should add connectivity test
-            boolean connectivityTest = 
FileSystemFactory.get(brokerDesc.getStorageProperties())
-                    .connectivityTest(filePaths);
-            if (!connectivityTest) {
-                throw new UserException("Failed to access object storage, 
message=connectivity test failed");
-            }
         }
     }
 
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/COSPropertiesTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/COSPropertiesTest.java
index 294f9c2bea9..adb59dbf249 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/COSPropertiesTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/COSPropertiesTest.java
@@ -143,4 +143,20 @@ public class COSPropertiesTest {
         //not support this case
         Assertions.assertThrowsExactly(StoragePropertiesException.class, () -> 
StorageProperties.createPrimary(cosNoEndpointProps), "Property cos.endpoint is 
required.");
     }
+
+    @Test
+    public void testMissingAccessKey() {
+        origProps.put("cos.endpoint", "cos.ap-beijing.myqcloud.com");
+        origProps.put("cos.secret_key", "myCOSSecretKey");
+        Assertions.assertThrows(StoragePropertiesException.class, () -> 
StorageProperties.createPrimary(origProps),
+                 "Please set access_key and secret_key or omit both for 
anonymous access to public bucket.");
+    }
+
+    @Test
+    public void testMissingSecretKey() {
+        origProps.put("cos.endpoint", "cos.ap-beijing.myqcloud.com");
+        origProps.put("cos.access_key", "myCOSAccessKey");
+        Assertions.assertThrows(StoragePropertiesException.class, () -> 
StorageProperties.createPrimary(origProps),
+                 "Please set access_key and secret_key or omit both for 
anonymous access to public bucket.");
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/MinioPropertiesTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/MinioPropertiesTest.java
index 994a00ad1c8..d4418d2f36d 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/MinioPropertiesTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/MinioPropertiesTest.java
@@ -18,6 +18,7 @@
 package org.apache.doris.datasource.property.storage;
 
 import org.apache.doris.common.UserException;
+import 
org.apache.doris.datasource.property.storage.exception.StoragePropertiesException;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -67,8 +68,18 @@ public class MinioPropertiesTest {
     public void testMissingAccessKey() {
         origProps.put("s3.endpoint", "http://localhost:9000";);
         origProps.put("s3.secret_key", "minioSecretKey");
-        Assertions.assertThrows(IllegalArgumentException.class, () ->
-                StorageProperties.createPrimary(origProps), "Property 
s3.access_key is required.");
+        Assertions.assertThrows(StoragePropertiesException.class, () ->
+                         StorageProperties.createPrimary(origProps),
+                         "Please set access_key and secret_key or omit both 
for anonymous access to public bucket.");
+    }
+
+    @Test
+    public void testMissingSecretKey() {
+        origProps.put("s3.endpoint", "http://localhost:9000";);
+        origProps.put("s3.access_key", "minioAccessKey");
+        Assertions.assertThrows(StoragePropertiesException.class, () ->
+                         StorageProperties.createPrimary(origProps),
+                         "Please set access_key and secret_key or omit both 
for anonymous access to public bucket.");
     }
 
     @Test
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OBSPropertyTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OBSPropertyTest.java
index a5993742837..e6c7fe3d582 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OBSPropertyTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OBSPropertyTest.java
@@ -18,6 +18,7 @@
 package org.apache.doris.datasource.property.storage;
 
 import org.apache.doris.common.UserException;
+import 
org.apache.doris.datasource.property.storage.exception.StoragePropertiesException;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -122,6 +123,22 @@ public class OBSPropertyTest {
         Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> 
StorageProperties.createPrimary(cosNoEndpointProps), "Property cos.endpoint is 
required.");
     }
 
+    @Test
+    public void testmissingAccessKey() {
+        origProps.put("obs.endpoint", "obs.cn-north-4.myhuaweicloud.com");
+        origProps.put("obs.secret_key", "myCOSSecretKey");
+        Assertions.assertThrows(StoragePropertiesException.class, () -> 
StorageProperties.createPrimary(origProps),
+                 "Please set access_key and secret_key or omit both for 
anonymous access to public bucket.");
+    }
+
+    @Test
+    public void testMissingSecretKey() {
+        origProps.put("obs.endpoint", "obs.cn-north-4.myhuaweicloud.com");
+        origProps.put("obs.access_key", "myCOSAccessKey");
+        Assertions.assertThrows(StoragePropertiesException.class, () -> 
StorageProperties.createPrimary(origProps),
+                 "Please set access_key and secret_key or omit both for 
anonymous access to public bucket.");
+    }
+
     private static String obsAccessKey = "";
     private static String obsSecretKey = "";
 
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OSSPropertiesTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OSSPropertiesTest.java
index 3f0c4c688bd..0df7c5d6be9 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OSSPropertiesTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/OSSPropertiesTest.java
@@ -138,4 +138,22 @@ public class OSSPropertiesTest {
         // not support
         Assertions.assertThrowsExactly(StoragePropertiesException.class, () -> 
StorageProperties.createPrimary(cosNoEndpointProps), "Property cos.endpoint is 
required.");
     }
+
+    @Test
+    public void testMissingAccessKey() {
+        Map<String, String> origProps = new HashMap<>();
+        origProps.put("oss.endpoint", "oss-cn-hangzhou.aliyuncs.com");
+        origProps.put("oss.secret_key", "myOSSSecretKey");
+        Assertions.assertThrows(StoragePropertiesException.class, () -> 
StorageProperties.createPrimary(origProps),
+                 "Please set access_key and secret_key or omit both for 
anonymous access to public bucket.");
+    }
+
+    @Test
+    public void testMissingSecretKey() {
+        Map<String, String> origProps = new HashMap<>();
+        origProps.put("oss.endpoint", "oss-cn-hangzhou.aliyuncs.com");
+        origProps.put("oss.access_key", "myOSSAccessKey");
+        Assertions.assertThrows(StoragePropertiesException.class, () -> 
StorageProperties.createPrimary(origProps),
+                 "Please set access_key and secret_key or omit both for 
anonymous access to public bucket.");
+    }
 }
diff --git 
a/regression-test/data/load_p0/broker_load/test_s3_load_without_aksk.out 
b/regression-test/data/load_p0/broker_load/test_s3_load_without_aksk.out
new file mode 100644
index 00000000000..8b0290edbdd
Binary files /dev/null and 
b/regression-test/data/load_p0/broker_load/test_s3_load_without_aksk.out differ
diff --git a/regression-test/data/load_p0/tvf/test_tvf_without_aksk.out 
b/regression-test/data/load_p0/tvf/test_tvf_without_aksk.out
new file mode 100644
index 00000000000..8b0290edbdd
Binary files /dev/null and 
b/regression-test/data/load_p0/tvf/test_tvf_without_aksk.out differ
diff --git 
a/regression-test/suites/external_table_p0/refactor_storage_param/test_s3_tvf_s3_storage.groovy
 
b/regression-test/suites/external_table_p0/refactor_storage_param/test_s3_tvf_s3_storage.groovy
index c4ce3595bf8..a9ffd27129d 100644
--- 
a/regression-test/suites/external_table_p0/refactor_storage_param/test_s3_tvf_s3_storage.groovy
+++ 
b/regression-test/suites/external_table_p0/refactor_storage_param/test_s3_tvf_s3_storage.groovy
@@ -181,7 +181,7 @@ suite("test_s3_tvf_s3_storage", 
"p0,external,external_docker") {
         s3_tvf("http://${bucket}.${s3_endpoint}";, "", "s3.access_key", 
"s3.secret_key", "s3.region", "false");
         shouldFail {
             // it's OSS 
-            s3_tvf("http://${bucket}.${s3_endpoint}";, "", "cos.access_key", 
"cos.secret_key", "region", "false");
+            s3_tvf("http://${bucket}.${s3_endpoint}";, "", "s3.access_key", 
"cos.secret_key", "region", "false");
         }
         s3_tvf("http://${bucket}.${s3_endpoint}";, "", "s3.access_key", 
"s3.secret_key", "region", "false");
         //endpoint field is no valid, so we extract the endpoint from uri
@@ -230,7 +230,7 @@ suite("test_s3_tvf_s3_storage", 
"p0,external,external_docker") {
         s3_tvf("http://${bucket}.${s3_endpoint}";, "", "AWS_ACCESS_KEY", 
"AWS_SECRET_KEY", "region", "false");
         s3_tvf("http://${bucket}.${s3_endpoint}";, "", "s3.access_key", 
"s3.secret_key", "s3.region", "false");
         shouldFail {
-            s3_tvf("http://${bucket}.${s3_endpoint}";, "", "cos.access_key", 
"cos.secret_key", "region", "false");
+            s3_tvf("http://${bucket}.${s3_endpoint}";, "", "cos.access_key", 
"s3.secret_key", "region", "false");
         }
         s3_tvf("http://${bucket}.${s3_endpoint}";, "", "s3.access_key", 
"s3.secret_key", "region", "false");
         s3_tvf("http://${bucket}.${s3_endpoint}";, "cos.endpoint", 
"s3.access_key", "s3.secret_key", "region", "false");
@@ -256,7 +256,7 @@ suite("test_s3_tvf_s3_storage", 
"p0,external,external_docker") {
             s3_tvf("s3://${bucket}", "cos.endpoint", "cos.access_key", 
"cos.secret_key", "cos.region", "false");
         }
         shouldFail{
-            s3_tvf("s3://${bucket}", "s3.endpoint", "cos.access_key", 
"cos.secret_key", "cos.region", "false");  
+            s3_tvf("s3://${bucket}", "s3.endpoint", "cos.access_key", 
"s3.secret_key", "cos.region", "false");  
         }
         s3_tvf("cos://${bucket}", "s3.endpoint", "s3.access_key", 
"s3.secret_key", "region", "false");
         s3_tvf("cos://${bucket}", "s3.endpoint", "s3.access_key", 
"s3.secret_key", "region", "false");
diff --git 
a/regression-test/suites/load_p0/broker_load/test_domain_connection_and_ak_sk_correction.groovy
 
b/regression-test/suites/load_p0/broker_load/test_domain_connection_and_ak_sk_correction.groovy
index 76ffbf47221..8f8ffac9c98 100644
--- 
a/regression-test/suites/load_p0/broker_load/test_domain_connection_and_ak_sk_correction.groovy
+++ 
b/regression-test/suites/load_p0/broker_load/test_domain_connection_and_ak_sk_correction.groovy
@@ -105,61 +105,6 @@ suite("test_domain_connection_and_ak_sk_correction",  
"load_p0") {
         assertTrue(e.getMessage().contains("Invalid endpoint format"), 
e.getMessage())
     }
 
-    label = UUID.randomUUID().toString().replace("-", "")
-    try {
-        result = sql """
-            LOAD LABEL ${label}
-            (
-                DATA 
INFILE("s3://${getS3BucketName()}/regression/tpch/sf1/part.tbl")
-                INTO TABLE ${tableName}
-                COLUMNS TERMINATED BY "|"
-                (p_partkey, p_name, p_mfgr, p_brand, p_type, p_size, 
p_container, p_retailprice, p_comment, temp)
-            )
-            WITH S3
-            (
-                "AWS_ENDPOINT" = "${getS3Endpoint()}",
-                "AWS_ACCESS_KEY" = "${getS3AK()}1",
-                "AWS_SECRET_KEY" = "${getS3SK()}",
-                "AWS_REGION" = "${getS3Region()}",
-                "PROVIDER" = "${getS3Provider()}"
-            );
-        """
-        logger.info("the third sql result is {}", result)
-        assertTrue(false. "AK is wrong, so the correction of AKSK test should 
fale")
-    } catch (Exception e) {
-        logger.info("the third sql exception result is {}", e.getMessage())
-        assertTrue(e.getMessage().contains("Failed to access object storage, 
message="), e.getMessage())
-    }
-
-    label = UUID.randomUUID().toString().replace("-", "")
-    try {
-        result = sql """
-            LOAD LABEL ${label}
-            (
-                DATA 
INFILE("s3://${getS3BucketName()}/regression/tpch/sf1/part.tbl")
-                INTO TABLE ${tableName}
-                COLUMNS TERMINATED BY "|"
-                (p_partkey, p_name, p_mfgr, p_brand, p_type, p_size, 
p_container, p_retailprice, p_comment, temp),
-                DATA 
INFILE("s3://${getS3BucketName()}1/regression/tpch/sf1/orders.tbl.1", 
"s3://${getS3BucketName()}/regression/tpch/sf1/orders.tbl.2")
-                INTO TABLE ${tableNameOrders}
-                COLUMNS TERMINATED BY "|"
-                (o_orderkey, o_custkey, o_orderstatus, o_totalprice, 
o_orderdate, o_orderpriority, o_clerk, o_shippriority, o_comment, temp)
-            )
-            WITH S3
-            (
-                "AWS_ENDPOINT" = "${getS3Endpoint()}",
-                "AWS_ACCESS_KEY" = "${getS3AK()}",
-                "AWS_SECRET_KEY" = "${getS3SK()}",
-                "AWS_REGION" = "${getS3Region()}",
-                "PROVIDER" = "${getS3Provider()}"
-            );
-        """
-        logger.info("the fourth sql result is {}", result)
-        assertTrue(false. "in the second DATA INFILE, the first bucket is 
wrong, so the sql should fail")
-    } catch (Exception e) {
-        logger.info("the fourth sql exception result is {}", e.getMessage())
-        assertTrue(e.getMessage().contains("Failed to access object storage, 
message="), e.getMessage())
-    }
     sql """ DROP TABLE IF EXISTS ${tableName} FORCE"""
     sql """ DROP TABLE IF EXISTS ${tableNameOrders} FORCE"""
 }
diff --git 
a/regression-test/suites/load_p0/broker_load/test_s3_load_without_aksk.groovy 
b/regression-test/suites/load_p0/broker_load/test_s3_load_without_aksk.groovy
new file mode 100644
index 00000000000..60b39c03657
--- /dev/null
+++ 
b/regression-test/suites/load_p0/broker_load/test_s3_load_without_aksk.groovy
@@ -0,0 +1,111 @@
+// 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.
+
+suite("test_s3_load_without_aksk", "load_p0") {
+    def tableName = "tbl_without_aksk"
+
+    sql """ DROP TABLE IF EXISTS ${tableName} """
+
+    sql """
+        CREATE TABLE ${tableName} (
+             user_id            BIGINT       NOT NULL COMMENT "用户 ID",
+             name               VARCHAR(20)           COMMENT "用户姓名",
+             age                INT                   COMMENT "用户年龄"
+         ) DUPLICATE KEY(user_id)
+         DISTRIBUTED BY HASH(user_id) BUCKETS 1
+         PROPERTIES (
+             "replication_num" = "1"
+         )
+    """
+
+    def label = UUID.randomUUID().toString().replace("-", "0")
+
+    def sql_str = """
+            LOAD LABEL $label (
+                DATA 
INFILE("s3://${s3BucketName}/regression/load/data/example_0.csv")
+                INTO TABLE $tableName
+                COLUMNS TERMINATED BY ","
+            )
+            WITH S3 (
+                "AWS_ENDPOINT" = "${getS3Endpoint()}",
+                "PROVIDER" = "${getS3Provider()}"
+            )
+            """
+    logger.info("submit sql: ${sql_str}");
+    sql """${sql_str}"""
+
+    def max_try_milli_secs = 600000
+    while (max_try_milli_secs > 0) {
+        String[][] result = sql """ show load where label="$label" order by 
createtime desc limit 1; """
+        if (result[0][2].equals("FINISHED")) {
+            logger.info("Load FINISHED " + label)
+            break
+        }
+        if (result[0][2].equals("CANCELLED")) {
+            def reason = result[0][7]
+            logger.info("load failed, reason:$reason")
+            assertTrue(1 == 2)
+            break
+        }
+        Thread.sleep(1000)
+        max_try_milli_secs -= 1000
+        if(max_try_milli_secs <= 0) {
+            assertTrue(1 == 2, "load Timeout: $label")
+        }
+    }
+
+    qt_sql """ SELECT * FROM ${tableName} order by user_id """
+
+    label = UUID.randomUUID().toString().replace("-", "0")
+
+    sql_str = """
+            LOAD LABEL $label (
+                DATA 
INFILE("s3://${s3BucketName}/regression/load/data/example_*.csv")
+                INTO TABLE $tableName
+                COLUMNS TERMINATED BY ","
+            )
+            WITH S3 (
+                "s3.endpoint" = "${getS3Endpoint()}",
+                "PROVIDER" = "${getS3Provider()}"
+            )
+            """
+    logger.info("submit sql: ${sql_str}");
+    sql """${sql_str}"""
+
+    max_try_milli_secs = 600000
+    while (max_try_milli_secs > 0) {
+        String[][] result = sql """ show load where label="$label" order by 
createtime desc limit 1; """
+        if (result[0][2].equals("FINISHED")) {
+            logger.info("Load FINISHED " + label)
+            break
+        }
+        if (result[0][2].equals("CANCELLED")) {
+            def reason = result[0][7]
+            logger.info("load failed, reason:$reason")
+            assertTrue(1 == 2)
+            break
+        }
+        Thread.sleep(1000)
+        max_try_milli_secs -= 1000
+        if(max_try_milli_secs <= 0) {
+            assertTrue(1 == 2, "load Timeout: $label")
+        }
+    }
+
+    qt_sql """ SELECT * FROM ${tableName} order by user_id """
+
+}
diff --git a/regression-test/suites/load_p0/tvf/test_tvf_without_aksk.groovy 
b/regression-test/suites/load_p0/tvf/test_tvf_without_aksk.groovy
new file mode 100644
index 00000000000..622fd5b8276
--- /dev/null
+++ b/regression-test/suites/load_p0/tvf/test_tvf_without_aksk.groovy
@@ -0,0 +1,60 @@
+// 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.
+
+suite("test_tvf_without_aksk", "load_p0") {
+    def tableName = "tbl_without_aksk"
+
+    sql """ DROP TABLE IF EXISTS ${tableName} """
+
+    sql """
+        CREATE TABLE ${tableName} (
+             user_id            BIGINT       NOT NULL COMMENT "用户 ID",
+             name               VARCHAR(20)           COMMENT "用户姓名",
+             age                INT                   COMMENT "用户年龄"
+         ) DUPLICATE KEY(user_id)
+         DISTRIBUTED BY HASH(user_id) BUCKETS 1
+         PROPERTIES (
+             "replication_num" = "1"
+         )
+    """
+
+    def label = UUID.randomUUID().toString().replace("-", "0")
+
+    sql """
+            INSERT INTO ${tableName}
+                SELECT * FROM S3
+                (
+                    "uri" = 
"s3://${s3BucketName}/regression/load/data/example_0.csv",
+                    "s3.endpoint" = "${getS3Endpoint()}",
+                    "column_separator" = ",",
+                    "format" = "csv"
+                );
+     """
+    qt_sql """ SELECT * FROM ${tableName} order by user_id """
+
+    sql """
+            INSERT INTO ${tableName}
+                SELECT * FROM S3
+                (
+                    "uri" = 
"s3://${s3BucketName}/regression/load/data/example_*.csv",
+                    "s3.endpoint" = "${getS3Endpoint()}",
+                    "column_separator" = ",",
+                    "format" = "csv"
+                );
+     """
+    qt_sql """ SELECT * FROM ${tableName} order by user_id """
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to