This is an automated email from the ASF dual-hosted git repository. morningman pushed a commit to branch branch-refactor_property in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-refactor_property by this push: new 3d6f67696a0 [Test](refactor-storage)Add BACKUP/RESTORE/REPOSITORY Test (#49372) 3d6f67696a0 is described below commit 3d6f67696a08df0f62d2a7422064c14c5d1d99a1 Author: Calvin Kirs <guoqi...@selectdb.com> AuthorDate: Mon Mar 24 14:39:34 2025 +0800 [Test](refactor-storage)Add BACKUP/RESTORE/REPOSITORY Test (#49372) ### What problem does this PR solve? This PR continues the optimization of the S3Filesystem by removing deprecated parameters and adapting the Backup&Restore logic to use the new parameter format. Additionally, it introduces relevant test cases to ensure compatibility and correctness. #### Main Changes Remove Legacy Parameters from S3Filesystem Clean up outdated S3 parameters to simplify the codebase and eliminate potential compatibility issues. #### Adapt BackupRestore to Use New Parameters Modify Backup & Restore to correctly parse and use the new parameter format in S3Filesystem, ensuring stable backup and restore functionality. #### Add Test Cases Introduce unit tests and integration tests for S3 to verify compatibility with the new parameters and validate the BackupRestore logic. --- .../java/org/apache/doris/analysis/LoadStmt.java | 2 + .../doris/datasource/property/PropertyUtils.java | 18 ++- .../storage/AbstractObjectStorageProperties.java | 24 +-- .../datasource/property/storage/COSProperties.java | 17 +- .../datasource/property/storage/OBSProperties.java | 14 +- .../datasource/property/storage/OSSProperties.java | 16 +- .../property/storage/ObjectStorageProperties.java | 8 + .../datasource/property/storage/S3Properties.java | 22 ++- .../java/org/apache/doris/fs/obj/S3ObjStorage.java | 50 ++---- .../org/apache/doris/fs/remote/S3FileSystem.java | 4 +- .../property/storage/COSPropertiesTest.java | 39 ++++- .../property/storage/OBSPropertyTest.java | 20 +++ .../property/storage/OSSPropertiesTest.java | 23 +++ .../property/storage/S3PropertiesTest.java | 34 +++- .../org/apache/doris/fs/obj/S3FileSystemTest.java | 7 +- .../org/apache/doris/fs/obj/S3ObjStorageTest.java | 18 ++- .../backup_restore_cos.groovy | 178 +++++++++++++++++++++ 17 files changed, 423 insertions(+), 71 deletions(-) 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 9ceec9415e3..2863f7c8a18 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 @@ -504,6 +504,8 @@ public class LoadStmt extends DdlStmt implements NotFallbackInParser { } } else if (brokerDesc != null) { etlJobType = EtlJobType.BROKER; + //todo Storage-related parameter validation should not be placed here. + // This section should focus solely on business logic. checkS3Param(); } else if (isMysqlLoad) { etlJobType = EtlJobType.LOCAL_FILE; diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/PropertyUtils.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/PropertyUtils.java index 86c25655db7..f02f01efad3 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/PropertyUtils.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/PropertyUtils.java @@ -27,15 +27,19 @@ public class PropertyUtils { // Get all fields of a class with annotation @ConnectorProperty public static List<Field> getConnectorProperties(Class<?> clazz) { List<Field> fields = Lists.newArrayList(); - for (Field field : clazz.getDeclaredFields()) { - field.setAccessible(true); - if (field.isAnnotationPresent(ConnectorProperty.class)) { - // Get annotation of the field - ConnectorProperty connectorProperty = field.getAnnotation(ConnectorProperty.class); - if (connectorProperty.supported()) { - fields.add(field); + Class<?> currentClass = clazz; + + while (currentClass != null) { + for (Field field : currentClass.getDeclaredFields()) { + field.setAccessible(true); + if (field.isAnnotationPresent(ConnectorProperty.class)) { + ConnectorProperty connectorProperty = field.getAnnotation(ConnectorProperty.class); + if (connectorProperty.supported()) { + fields.add(field); + } } } + currentClass = currentClass.getSuperclass(); } return fields; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractObjectStorageProperties.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractObjectStorageProperties.java index b9d84d2a7c9..7d177d3bd3d 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractObjectStorageProperties.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/AbstractObjectStorageProperties.java @@ -41,24 +41,25 @@ public abstract class AbstractObjectStorageProperties extends StorageProperties * This value is optional and can be configured by the user. */ @Getter - @ConnectorProperty(names = {"maxConnections"}, required = false, description = "Maximum number of connections.") - protected int maxConnections = 100; + @ConnectorProperty(names = {"connection.maximum"}, required = false, description = "Maximum number of connections.") + protected String maxConnections = "100"; /** * The timeout (in milliseconds) for requests made to the object storage system. * This value is optional and can be configured by the user. */ @Getter - @ConnectorProperty(names = {"requestTimeoutS"}, required = false, description = "Request timeout in seconds.") - protected int requestTimeoutS = 10000; + @ConnectorProperty(names = {"connection.request.timeout"}, required = false, + description = "Request timeout in seconds.") + protected String requestTimeoutS = "10000"; /** * The timeout (in milliseconds) for establishing a connection to the object storage system. * This value is optional and can be configured by the user. */ @Getter - @ConnectorProperty(names = {"connectionTimeoutS"}, required = false, description = "Connection timeout in seconds.") - protected int connectionTimeoutS = 10000; + @ConnectorProperty(names = {"connection.timeout"}, required = false, description = "Connection timeout in seconds.") + protected String connectionTimeoutS = "10000"; /** * Flag indicating whether to use path-style URLs for the object storage system. @@ -66,9 +67,14 @@ public abstract class AbstractObjectStorageProperties extends StorageProperties */ @Setter @Getter - @ConnectorProperty(names = {"usePathStyle", "s3.path-style-access"}, required = false, + @ConnectorProperty(names = {"use_path_style", "s3.path-style-access"}, required = false, description = "Whether to use path style URL for the storage.") - protected boolean usePathStyle = false; + protected String usePathStyle = "false"; + @ConnectorProperty(names = {"force_parsing_by_standard_uri"}, required = false, + description = "Whether to use path style URL for the storage.") + @Setter + @Getter + protected String forceParsingByStandardUrl = "false"; /** * Constructor to initialize the object storage properties with the provided type and original properties map. @@ -124,7 +130,7 @@ public abstract class AbstractObjectStorageProperties extends StorageProperties String accessKey, String secretKey) { return generateAWSS3Properties(endpoint, region, accessKey, secretKey, String.valueOf(getMaxConnections()), String.valueOf(getRequestTimeoutS()), - String.valueOf(getConnectionTimeoutS()), String.valueOf(isUsePathStyle())); + String.valueOf(getConnectionTimeoutS()), usePathStyle); } @Override 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 1791ba67a51..5416819dd36 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 @@ -71,7 +71,7 @@ public class COSProperties extends AbstractObjectStorageProperties { config.putAll(generateAWSS3Properties(cosEndpoint, getRegion(), cosAccessKey, cosSecretKey)); } - private String getRegion() { + public String getRegion() { if (Strings.isNullOrEmpty(this.cosRegion)) { if (cosEndpoint.contains("myqcloud.com")) { Pattern cosPattern = Pattern.compile("cos\\.([a-z0-9-]+)\\.myqcloud\\.com"); @@ -83,4 +83,19 @@ public class COSProperties extends AbstractObjectStorageProperties { } return this.cosRegion; } + + @Override + public String getEndpoint() { + return cosEndpoint; + } + + @Override + public String getAccessKey() { + return cosAccessKey; + } + + @Override + public String getSecretKey() { + return cosSecretKey; + } } 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 1cdb4a83139..d561eb5fdca 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 @@ -65,7 +65,7 @@ public class OBSProperties extends AbstractObjectStorageProperties { config.putAll(generateAWSS3Properties(obsEndpoint, getRegion(), obsAccessKey, obsSecretKey)); } - private String getRegion() { + public String getRegion() { if (Strings.isNullOrEmpty(this.region) && obsEndpoint.contains("myhuaweicloud.com")) { Pattern obsPattern = Pattern.compile("obs\\.([a-z0-9-]+)\\.myhuaweicloud\\.com"); Matcher matcher = obsPattern.matcher(obsEndpoint); @@ -75,4 +75,16 @@ public class OBSProperties extends AbstractObjectStorageProperties { } return this.region; } + + public String getEndpoint() { + return obsEndpoint; + } + + public String getAccessKey() { + return obsAccessKey; + } + + public String getSecretKey() { + return obsSecretKey; + } } 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 712a1ab61e5..3a5ede7a539 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 @@ -51,6 +51,7 @@ public class OSSProperties extends AbstractObjectStorageProperties { public Configuration getHadoopConfiguration() { Configuration conf = new Configuration(false); conf.set("fs.oss.endpoint", endpoint); + conf.set("fs.oss.region", getRegion()); conf.set("fs.oss.accessKeyId", accessKey); conf.set("fs.oss.accessKeySecret", secretKey); conf.set("fs.oss.impl", "org.apache.hadoop.fs.aliyun.oss.AliyunOSSFileSystem"); @@ -62,7 +63,7 @@ public class OSSProperties extends AbstractObjectStorageProperties { config.putAll(generateAWSS3Properties(endpoint, getRegion(), accessKey, secretKey)); } - private String getRegion() { + public String getRegion() { // Return the region if it is already set if (!Strings.isNullOrEmpty(this.region)) { return region; @@ -91,5 +92,18 @@ public class OSSProperties extends AbstractObjectStorageProperties { return this.region; } + @Override + public String getEndpoint() { + return endpoint; + } + @Override + public String getAccessKey() { + return accessKey; + } + + @Override + public String getSecretKey() { + return secretKey; + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/ObjectStorageProperties.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/ObjectStorageProperties.java index 2465fbf6192..22694293e25 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/ObjectStorageProperties.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/storage/ObjectStorageProperties.java @@ -48,4 +48,12 @@ public interface ObjectStorageProperties { * to the object storage system. */ void toNativeS3Configuration(Map<String, String> config); + + String getEndpoint(); + + String getRegion(); + + String getAccessKey(); + + String getSecretKey(); } 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 ad4fcd2eb81..6963a0b024a 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 @@ -147,7 +147,7 @@ public class S3Properties extends AbstractObjectStorageProperties { catalogProps.put("s3.access-key-id", s3AccessKey); catalogProps.put("s3.secret-access-key", s3SecretKey); catalogProps.put("client.region", s3Region); - catalogProps.put("s3.path-style-access", Boolean.toString(usePathStyle)); + catalogProps.put("s3.path-style-access", usePathStyle); } @Override @@ -177,4 +177,24 @@ public class S3Properties extends AbstractObjectStorageProperties { toNativeS3Configuration(config); return config; } + + @Override + public String getEndpoint() { + return s3Endpoint; + } + + @Override + public String getRegion() { + return s3Region; + } + + @Override + public String getAccessKey() { + return s3AccessKey; + } + + @Override + public String getSecretKey() { + return s3SecretKey; + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/fs/obj/S3ObjStorage.java b/fe/fe-core/src/main/java/org/apache/doris/fs/obj/S3ObjStorage.java index edcb54bf8fa..a6411aea219 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/fs/obj/S3ObjStorage.java +++ b/fe/fe-core/src/main/java/org/apache/doris/fs/obj/S3ObjStorage.java @@ -23,8 +23,7 @@ import org.apache.doris.common.UserException; import org.apache.doris.common.credentials.CloudCredential; import org.apache.doris.common.util.S3URI; import org.apache.doris.common.util.S3Util; -import org.apache.doris.datasource.property.PropertyConverter; -import org.apache.doris.datasource.property.constants.S3Properties; +import org.apache.doris.datasource.property.storage.AbstractObjectStorageProperties; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Triple; @@ -68,11 +67,13 @@ public class S3ObjStorage implements ObjStorage<S3Client> { protected Map<String, String> properties; + protected AbstractObjectStorageProperties s3Properties; + private boolean isUsePathStyle = false; private boolean forceParsingByStandardUri = false; - public S3ObjStorage(Map<String, String> properties) { + public S3ObjStorage(AbstractObjectStorageProperties properties) { this.properties = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); setProperties(properties); } @@ -81,49 +82,28 @@ public class S3ObjStorage implements ObjStorage<S3Client> { return properties; } - protected void setProperties(Map<String, String> properties) { - this.properties.putAll(properties); - try { - S3Properties.requiredS3Properties(this.properties); - } catch (DdlException e) { - throw new IllegalArgumentException(e); - } - // Virtual hosted-style is recommended in the s3 protocol. - // The path-style has been abandoned, but for some unexplainable reasons, - // the s3 client will determine whether the endpiont starts with `s3` - // when generating a virtual hosted-sytle request. - // If not, it will not be converted ( https://github.com/aws/aws-sdk-java-v2/pull/763), - // but the endpoints of many cloud service providers for object storage do not start with s3, - // so they cannot be converted to virtual hosted-sytle. - // Some of them, such as aliyun's oss, only support virtual hosted-style, - // and some of them(ceph) may only support - // path-style, so we need to do some additional conversion. - isUsePathStyle = this.properties.getOrDefault(PropertyConverter.USE_PATH_STYLE, "false") - .equalsIgnoreCase("true"); - forceParsingByStandardUri = this.properties.getOrDefault(PropertyConverter.FORCE_PARSING_BY_STANDARD_URI, - "false").equalsIgnoreCase("true"); - - String endpoint = this.properties.get(S3Properties.ENDPOINT); - String region = this.properties.get(S3Properties.REGION); - - this.properties.put(S3Properties.REGION, PropertyConverter.checkRegion(endpoint, region, S3Properties.REGION)); + protected void setProperties(AbstractObjectStorageProperties properties) { + this.s3Properties = properties; + isUsePathStyle = Boolean.parseBoolean(properties.getUsePathStyle()); + forceParsingByStandardUri = Boolean.parseBoolean(s3Properties.getForceParsingByStandardUrl()); } @Override public S3Client getClient() throws UserException { if (client == null) { - String endpointStr = properties.get(S3Properties.ENDPOINT); + String endpointStr = s3Properties.getEndpoint(); if (!endpointStr.contains("://")) { endpointStr = "http://" + endpointStr; } URI endpoint = URI.create(endpointStr); CloudCredential credential = new CloudCredential(); - credential.setAccessKey(properties.get(S3Properties.ACCESS_KEY)); - credential.setSecretKey(properties.get(S3Properties.SECRET_KEY)); - if (properties.containsKey(S3Properties.SESSION_TOKEN)) { + credential.setAccessKey(s3Properties.getAccessKey()); + credential.setSecretKey(s3Properties.getSecretKey()); + + /* if (properties.containsKey(S3Properties.SESSION_TOKEN)) { credential.setSessionToken(properties.get(S3Properties.SESSION_TOKEN)); - } - client = S3Util.buildS3Client(endpoint, properties.get(S3Properties.REGION), credential, isUsePathStyle); + }*/ + client = S3Util.buildS3Client(endpoint, s3Properties.getRegion(), credential, isUsePathStyle); } return client; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/fs/remote/S3FileSystem.java b/fe/fe-core/src/main/java/org/apache/doris/fs/remote/S3FileSystem.java index 5910ed80c9e..555d1e42508 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/fs/remote/S3FileSystem.java +++ b/fe/fe-core/src/main/java/org/apache/doris/fs/remote/S3FileSystem.java @@ -49,7 +49,7 @@ public class S3FileSystem extends ObjFileSystem { public S3FileSystem(AbstractObjectStorageProperties s3Properties) { super(StorageBackend.StorageType.S3.name(), StorageBackend.StorageType.S3, - new S3ObjStorage(s3Properties.getOrigProps())); + new S3ObjStorage(s3Properties)); this.s3Properties = s3Properties; this.storageProperties = s3Properties; initFsProperties(); @@ -63,7 +63,7 @@ public class S3FileSystem extends ObjFileSystem { } private void initFsProperties() { - this.properties.putAll(((S3ObjStorage) objStorage).getProperties()); + this.properties.putAll(storageProperties.getOrigProps()); } @Override 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 18a0f710a86..b2148f8e53a 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 @@ -50,18 +50,30 @@ public class COSPropertiesTest { origProps.put("cos.access_key", "myCOSAccessKey"); origProps.put("cos.secret_key", "myCOSSecretKey"); origProps.put("cos.region", "us-west-1"); - origProps.put("cos.max_connections", "100"); - origProps.put("cos.request_timeout", "3000"); - origProps.put("cos.connection_timeout", "1000"); - origProps.put("cos.use_path_style", "true"); + origProps.put("connection.maximum", "88"); + origProps.put("connection.request.timeout", "100"); + origProps.put("connection.timeout", "1000"); + origProps.put("use_path_style", "true"); origProps.put(StorageProperties.FS_COS_SUPPORT, "true"); + origProps.put("test_non_storage_param", "6000"); + COSProperties cosProperties = (COSProperties) StorageProperties.create(origProps).get(1); Configuration config = cosProperties.getHadoopConfiguration(); + Map<String, String> cosConfig = cosProperties.getOrigProps(); + Assertions.assertTrue(!cosConfig.containsKey("test_non_storage_param")); + + origProps.forEach((k, v) -> { + if (!k.equals("test_non_storage_param") && !k.equals(StorageProperties.FS_COS_SUPPORT)) { + Assertions.assertEquals(v, cosConfig.get(k)); + } + }); // Validate the configuration Assertions.assertEquals("https://cos.example.com", config.get("fs.cos.endpoint")); Assertions.assertEquals("myCOSAccessKey", config.get("fs.cosn.userinfo.secretId")); Assertions.assertEquals("myCOSSecretKey", config.get("fs.cosn.userinfo.secretKey")); + Assertions.assertEquals("myCOSAccessKey", config.get("fs.cosn.userinfo.secretId")); + Assertions.assertEquals("myCOSSecretKey", config.get("fs.cosn.userinfo.secretKey")); origProps = new HashMap<>(); origProps.put("cos.endpoint", "https://cos.example.com"); origProps.put(StorageProperties.FS_COS_SUPPORT, "true"); @@ -78,18 +90,37 @@ public class COSPropertiesTest { origProps.put("cos.endpoint", "cos.ap-beijing.myqcloud.com"); origProps.put("cos.access_key", "myCOSAccessKey"); origProps.put("cos.secret_key", "myCOSSecretKey"); + origProps.put("test_non_storage_param", "6000"); + origProps.put("connection.maximum", "88"); + origProps.put("connection.request.timeout", "100"); + origProps.put("connection.timeout", "1000"); origProps.put(StorageProperties.FS_COS_SUPPORT, "true"); //origProps.put("cos.region", "ap-beijing"); COSProperties cosProperties = (COSProperties) StorageProperties.create(origProps).get(1); Map<String, String> s3Props = new HashMap<>(); cosProperties.toNativeS3Configuration(s3Props); + Map<String, String> cosConfig = cosProperties.getOrigProps(); + Assertions.assertTrue(!cosConfig.containsKey("test_non_storage_param")); + origProps.forEach((k, v) -> { + if (!k.equals("test_non_storage_param") && !k.equals(StorageProperties.FS_COS_SUPPORT)) { + Assertions.assertEquals(v, cosConfig.get(k)); + } + }); // Validate the S3 properties Assertions.assertEquals("cos.ap-beijing.myqcloud.com", s3Props.get("AWS_ENDPOINT")); Assertions.assertEquals("ap-beijing", s3Props.get("AWS_REGION")); Assertions.assertEquals("myCOSAccessKey", s3Props.get("AWS_ACCESS_KEY")); Assertions.assertEquals("myCOSSecretKey", s3Props.get("AWS_SECRET_KEY")); + Assertions.assertEquals("88", s3Props.get("AWS_MAX_CONNECTIONS")); + Assertions.assertEquals("100", s3Props.get("AWS_REQUEST_TIMEOUT_MS")); + Assertions.assertEquals("1000", s3Props.get("AWS_CONNECTION_TIMEOUT_MS")); + Assertions.assertEquals("false", s3Props.get("use_path_style")); + origProps.put("use_path_style", "true"); + cosProperties = (COSProperties) StorageProperties.create(origProps).get(1); + cosProperties.toNativeS3Configuration(s3Props); + Assertions.assertEquals("true", s3Props.get("use_path_style")); // Add any additional assertions for other properties if needed } 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 d4191d4be26..b7f3b521044 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 @@ -67,16 +67,36 @@ public class OBSPropertyTest { origProps.put("obs.access_key", "myOBSAccessKey"); origProps.put("obs.secret_key", "myOBSSecretKey"); origProps.put("obs.endpoint", "obs.cn-north-4.myhuaweicloud.com"); + origProps.put("connection.maximum", "88"); + origProps.put("connection.request.timeout", "100"); + origProps.put("connection.timeout", "1000"); + origProps.put("use_path_style", "true"); origProps.put(StorageProperties.FS_OBS_SUPPORT, "true"); OBSProperties obsProperties = (OBSProperties) StorageProperties.create(origProps).get(1); Map<String, String> s3Props = new HashMap<>(); + Map<String, String> obsConfig = obsProperties.getOrigProps(); + Assertions.assertTrue(!obsConfig.containsKey("test_non_storage_param")); + origProps.forEach((k, v) -> { + if (!k.equals("test_non_storage_param") && !k.equals(StorageProperties.FS_OBS_SUPPORT)) { + Assertions.assertEquals(v, obsConfig.get(k)); + } + }); obsProperties.toNativeS3Configuration(s3Props); Assertions.assertEquals("obs.cn-north-4.myhuaweicloud.com", s3Props.get("AWS_ENDPOINT")); Assertions.assertEquals("cn-north-4", s3Props.get("AWS_REGION")); Assertions.assertEquals("myOBSAccessKey", s3Props.get("AWS_ACCESS_KEY")); Assertions.assertEquals("myOBSSecretKey", s3Props.get("AWS_SECRET_KEY")); + Assertions.assertEquals("88", s3Props.get("AWS_MAX_CONNECTIONS")); + Assertions.assertEquals("100", s3Props.get("AWS_REQUEST_TIMEOUT_MS")); + Assertions.assertEquals("1000", s3Props.get("AWS_CONNECTION_TIMEOUT_MS")); + Assertions.assertEquals("true", s3Props.get("use_path_style")); + origProps.remove("use_path_style"); + obsProperties = (OBSProperties) StorageProperties.create(origProps).get(1); + s3Props = new HashMap<>(); + obsProperties.toNativeS3Configuration(s3Props); + Assertions.assertEquals("false", s3Props.get("use_path_style")); } private static String obsAccessKey = ""; 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 8002f8781fa..e582da4307e 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 @@ -63,15 +63,38 @@ public class OSSPropertiesTest { origProps.put("oss.secret_key", "myOSSSecretKey"); origProps.put("oss.endpoint", "oss-cn-beijing-internal.aliyuncs.com"); origProps.put(StorageProperties.FS_OSS_SUPPORT, "true"); + origProps.put("connection.maximum", "88"); + origProps.put("connection.request.timeout", "100"); + origProps.put("connection.timeout", "1000"); + origProps.put("use_path_style", "true"); + origProps.put("test_non_storage_param", "6000"); OSSProperties ossProperties = (OSSProperties) StorageProperties.create(origProps).get(1); Map<String, String> s3Props = new HashMap<>(); + Map<String, String> ossConfig = ossProperties.getOrigProps(); + Assertions.assertTrue(!ossConfig.containsKey("test_non_storage_param")); + + origProps.forEach((k, v) -> { + if (!k.equals("test_non_storage_param") && !k.equals(StorageProperties.FS_OSS_SUPPORT)) { + Assertions.assertEquals(v, ossConfig.get(k)); + } + }); + ossProperties.toNativeS3Configuration(s3Props); Assertions.assertEquals("oss-cn-beijing-internal.aliyuncs.com", s3Props.get("AWS_ENDPOINT")); Assertions.assertEquals("cn-beijing-internal", s3Props.get("AWS_REGION")); Assertions.assertEquals("myOSSAccessKey", s3Props.get("AWS_ACCESS_KEY")); Assertions.assertEquals("myOSSSecretKey", s3Props.get("AWS_SECRET_KEY")); + Assertions.assertEquals("88", s3Props.get("AWS_MAX_CONNECTIONS")); + Assertions.assertEquals("100", s3Props.get("AWS_REQUEST_TIMEOUT_MS")); + Assertions.assertEquals("1000", s3Props.get("AWS_CONNECTION_TIMEOUT_MS")); + Assertions.assertEquals("true", s3Props.get("use_path_style")); + origProps.remove("use_path_style"); + ossProperties = (OSSProperties) StorageProperties.create(origProps).get(1); + s3Props = new HashMap<>(); + ossProperties.toNativeS3Configuration(s3Props); + Assertions.assertEquals("false", s3Props.get("use_path_style")); } diff --git a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/S3PropertiesTest.java b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/S3PropertiesTest.java index 0e2085b90cc..dd0536f9c82 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/S3PropertiesTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/datasource/property/storage/S3PropertiesTest.java @@ -74,17 +74,42 @@ public class S3PropertiesTest { origProps.put("s3.secret_key", "myS3SecretKey"); origProps.put("s3.region", "us-west-1"); origProps.put(StorageProperties.FS_S3_SUPPORT, "true"); + origProps.put("use_path_style", "true"); + origProps.put("s3.connection.maximum", "88"); + origProps.put("s3.connection.timeout", "6000"); + origProps.put("test_non_storage_param", "6000"); + S3Properties s3Properties = (S3Properties) StorageProperties.create(origProps).get(1); Map<String, String> s3Props = new HashMap<>(); s3Properties.toNativeS3Configuration(s3Props); + Map<String, String> s3Config = s3Properties.getOrigProps(); + Assertions.assertTrue(!s3Config.containsKey("test_non_storage_param")); + + origProps.forEach((k, v) -> { + if (!k.equals("test_non_storage_param") && !k.equals(StorageProperties.FS_S3_SUPPORT)) { + Assertions.assertEquals(v, s3Config.get(k)); + } + }); + // Validate the S3 properties Assertions.assertEquals("https://cos.example.com", s3Props.get("AWS_ENDPOINT")); Assertions.assertEquals("us-west-1", s3Props.get("AWS_REGION")); Assertions.assertEquals("myS3AccessKey", s3Props.get("AWS_ACCESS_KEY")); Assertions.assertEquals("myS3SecretKey", s3Props.get("AWS_SECRET_KEY")); - // Add any additional assertions for other properties if needed + Assertions.assertEquals("88", s3Props.get("AWS_MAX_CONNECTIONS")); + Assertions.assertEquals("6000", s3Props.get("AWS_CONNECTION_TIMEOUT_MS")); + Assertions.assertEquals("true", s3Props.get("use_path_style")); + origProps.remove("use_path_style"); + origProps.remove("s3.connection.maximum"); + origProps.remove("s3.connection.timeout"); + s3Properties = (S3Properties) StorageProperties.create(origProps).get(1); + s3Props = new HashMap<>(); + s3Properties.toNativeS3Configuration(s3Props); + Assertions.assertEquals("false", s3Props.get("use_path_style")); + Assertions.assertEquals("50", s3Props.get("AWS_MAX_CONNECTIONS")); + Assertions.assertEquals("1000", s3Props.get("AWS_CONNECTION_TIMEOUT_MS")); } /** @@ -115,8 +140,15 @@ public class S3PropertiesTest { origProps.put("s3.secret_key", secretKey); origProps.put("s3.region", "ap-northeast-1"); origProps.put(StorageProperties.FS_S3_SUPPORT, "true"); + origProps.put("use_path_style", "true"); + origProps.put("s3.connection.maximum", "88"); + origProps.put("s3.connection.timeout", "6000"); + origProps.put("test_non_storage_param", "6000"); S3Properties s3Properties = (S3Properties) StorageProperties.create(origProps).get(1); Configuration configuration = s3Properties.getHadoopConfiguration(); + Assertions.assertEquals("88", configuration.get("fs.s3a.connection.maximum")); + Assertions.assertEquals("6000", configuration.get("fs.s3a.connection.timeout")); + Assertions.assertEquals("6000", configuration.get("fs.s3a.request.timeout")); FileSystem fs = FileSystem.get(new URI(hdfsPath), configuration); FileStatus[] fileStatuses = fs.listStatus(new Path(hdfsPath)); for (FileStatus status : fileStatuses) { diff --git a/fe/fe-core/src/test/java/org/apache/doris/fs/obj/S3FileSystemTest.java b/fe/fe-core/src/test/java/org/apache/doris/fs/obj/S3FileSystemTest.java index 442883573ce..7691d5580b7 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/fs/obj/S3FileSystemTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/fs/obj/S3FileSystemTest.java @@ -22,6 +22,8 @@ import org.apache.doris.backup.Status; import org.apache.doris.common.UserException; import org.apache.doris.common.util.S3URI; import org.apache.doris.datasource.property.PropertyConverter; +import org.apache.doris.datasource.property.storage.S3Properties; +import org.apache.doris.datasource.property.storage.StorageProperties; import org.apache.doris.fs.FileSystemFactory; import org.apache.doris.fs.remote.RemoteFile; import org.apache.doris.fs.remote.S3FileSystem; @@ -102,10 +104,11 @@ public class S3FileSystemTest { return mockedClient; } }; - S3ObjStorage mockedStorage = new S3ObjStorage(properties); + S3Properties s3Properties = (S3Properties) StorageProperties.createStorageProperties(properties); + S3ObjStorage mockedStorage = new S3ObjStorage(s3Properties); Assertions.assertTrue(mockedStorage.getClient() instanceof MockedS3Client); // inject storage to file system. - fileSystem = new S3FileSystem(mockedStorage); + fileSystem = new S3FileSystem(s3Properties); new MockUp<S3FileSystem>(S3FileSystem.class) { @Mock public Status globList(String remotePath, List<RemoteFile> result, boolean fileNameOnly) { diff --git a/fe/fe-core/src/test/java/org/apache/doris/fs/obj/S3ObjStorageTest.java b/fe/fe-core/src/test/java/org/apache/doris/fs/obj/S3ObjStorageTest.java index e565120600a..3688459f9e4 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/fs/obj/S3ObjStorageTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/fs/obj/S3ObjStorageTest.java @@ -19,6 +19,8 @@ package org.apache.doris.fs.obj; import org.apache.doris.backup.Status; import org.apache.doris.common.UserException; +import org.apache.doris.datasource.property.storage.S3Properties; +import org.apache.doris.datasource.property.storage.StorageProperties; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Assertions; @@ -56,7 +58,7 @@ class S3ObjStorageTest { properties.put("s3.access_key", ak); properties.put("s3.secret_key", sk); properties.put("s3.region", region); - S3ObjStorage storage = new S3ObjStorage(properties); + S3ObjStorage storage = new S3ObjStorage(null); String baseUrl = "s3://" + bucket + "/" + prefix + "/"; String content = "mocked"; @@ -109,7 +111,8 @@ class S3ObjStorageTest { properties.put("s3.endpoint", "s3.e.c"); properties.put("s3.access_key", "abc"); properties.put("s3.secret_key", "123"); - S3ObjStorage storage = new S3ObjStorage(properties); + S3Properties s3Properties = (S3Properties) StorageProperties.createStorageProperties(properties); + S3ObjStorage storage = new S3ObjStorage(s3Properties); Field client = storage.getClass().getDeclaredField("client"); client.setAccessible(true); MockedS3Client mockedClient = new MockedS3Client(); @@ -145,10 +148,11 @@ class S3ObjStorageTest { RemoteObject remoteObject = objectList.get(i); Assertions.assertEquals("key" + i, remoteObject.getRelativePath()); } + properties.put("use_path_style", "false"); + properties.put("s3.endpoint", "oss.a.c"); + S3Properties newS3Properties = (S3Properties) StorageProperties.createStorageProperties(properties); - storage.properties.put("use_path_style", "false"); - storage.properties.put("s3.endpoint", "oss.a.c"); - storage.setProperties(storage.properties); + storage.setProperties(newS3Properties); RemoteObjects remoteObjectsVBucket = storage.listObjects("oss://bucket/keys", null); List<RemoteObject> list = remoteObjectsVBucket.getObjectList(); for (int i = 0; i < list.size(); i++) { @@ -156,8 +160,8 @@ class S3ObjStorageTest { Assertions.assertTrue(remoteObject.getRelativePath().startsWith("key" + i)); } - storage.properties.put("use_path_style", "true"); - storage.setProperties(storage.properties); + properties.put("use_path_style", "true"); + storage.setProperties((S3Properties) StorageProperties.createStorageProperties(properties)); remoteObjectsVBucket = storage.listObjects("oss://bucket/keys", null); list = remoteObjectsVBucket.getObjectList(); for (int i = 0; i < list.size(); i++) { diff --git a/regression-test/suites/refactor_storage_param_p0/backup_restore_cos.groovy b/regression-test/suites/refactor_storage_param_p0/backup_restore_cos.groovy new file mode 100644 index 00000000000..884eafe2966 --- /dev/null +++ b/regression-test/suites/refactor_storage_param_p0/backup_restore_cos.groovy @@ -0,0 +1,178 @@ +// 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 org.awaitility.Awaitility; +import static java.util.concurrent.TimeUnit.SECONDS; +import static groovy.test.GroovyAssert.shouldFail +suite("refactor_storage_backup_restore_cos") { + String enabled= context.config.otherConfigs.get("enableBackUpRestoreCOSTest"); + if (enabled == null && enabled.equalsIgnoreCase("false")){ + return + } + def s3table = "test_backup_restore_cos"; + sql """ + drop table if exists ${s3table}; + """ + sql """ + CREATE TABLE ${s3table}( + user_id BIGINT NOT NULL COMMENT "user id", + name VARCHAR(20) COMMENT "name", + age INT COMMENT "age" + ) + DUPLICATE KEY(user_id) + DISTRIBUTED BY HASH(user_id) BUCKETS 10 + PROPERTIES ( + "replication_num" = "1" + ); + """ + sql """ + insert into ${s3table} values (1, 'a', 10); + """ + + def insertResult = sql """ + SELECT count(1) FROM ${s3table} + """ + + println "insertResult: ${insertResult}" + + assert insertResult.get(0).get(0) == 1 + def databaseQueryResult = sql """ + select database(); + """ + println databaseQueryResult + def currentDBName = databaseQueryResult.get(0).get(0) + println currentDBName + // cos + + String objectAccessKey = context.config.otherConfigs.get("cosAK"); + String objectSecretKey = context.config.otherConfigs.get("cosSK"); + String objectStorageEndpoint =context.config.otherConfigs.get("cosEndpoint"); + String objectStorageRegion = context.config.otherConfigs.get("cosRegion"); + String objectStorageFilePathPrefix =context.config.otherConfigs.get("cosFilePathPrefix"); + + def objectStorageRepoName = "cos_repo_test_1"; + try { + sql """ + drop repository ${objectStorageRepoName}; + """ + } catch (Exception e) { + //ignore exception, repo may not exist + } + + + shouldFail { + sql """ + CREATE REPOSITORY ${objectStorageRepoName} + WITH S3 + ON LOCATION "${objectStorageFilePathPrefix}" + PROPERTIES ( + "s3.endpoint" = "${objectStorageEndpoint}", + "s3.region" = "${objectStorageRegion}", + "s3.secret_key" = "${objectSecretKey}" + ); + """ + } + // Invalid export path https:// please use valid 's3://' path. + shouldFail { + objectStorageHttpsFilePathPrefix = objectStorageFilePathPrefix.replaceAll("^s3://", "https://"); + sql """ + CREATE REPOSITORY ${objectStorageRepoName}_https_prefix + WITH S3 + ON LOCATION "https://${objectStorageHttpsFilePathPrefix}" + PROPERTIES ( + "s3.endpoint" = "${objectStorageEndpoint}", + "s3.region" = "${objectStorageRegion}", + "s3.access_key" = "${objectAccessKey}", + "s3.secret_key" = "${objectSecretKey}" + ); + """ + } + shouldFail { + sql """ + CREATE REPOSITORY ${objectStorageRepoName} + WITH S3 + ON LOCATION "${objectStorageFilePathPrefix}" + PROPERTIES ( + "s3.endpoint" = "${objectStorageEndpoint}", + "s3.region" = "${objectStorageRegion}", + "s3.secret_key" = "${objectSecretKey}" + ); + """ + } + sql """ + CREATE REPOSITORY ${objectStorageRepoName} + WITH S3 + ON LOCATION "${objectStorageFilePathPrefix}" + PROPERTIES ( + "s3.endpoint" = "${objectStorageEndpoint}", + "s3.region" = "${objectStorageRegion}", + "s3.access_key" = "${objectAccessKey}", + "s3.secret_key" = "${objectSecretKey}" + ); + """ + + def objectStorageBackupLabel = "oss_label_1" + System.currentTimeMillis() + sql """ + BACKUP SNAPSHOT ${currentDBName}.${objectStorageBackupLabel} + TO ${objectStorageRepoName} + ON (${s3table}) + PROPERTIES ("type" = "full"); + + """ + Awaitility.await().atMost(60, SECONDS).pollInterval(5, SECONDS).until( + { + def backupResult = sql """ + show backup from ${currentDBName} where SnapshotName = '${objectStorageBackupLabel}'; + """ + println "backupResult: ${backupResult}" + return backupResult.get(0).get(3) == "FINISHED" + }) + + def queryCosSnapshotResult = sql """ + SHOW SNAPSHOT ON ${objectStorageRepoName} WHERE SNAPSHOT = '${objectStorageBackupLabel}'; + """ + println queryCosSnapshotResult + def objectSecretSnapshotTime = queryCosSnapshotResult.get(0).get(1) + sql """ + drop table if exists ${s3table}; + """ + //restore + sql """ + RESTORE SNAPSHOT ${currentDBName}.${objectStorageBackupLabel} + FROM ${objectStorageRepoName} + ON (`${s3table}`) + PROPERTIES + ( + "backup_timestamp"="${objectSecretSnapshotTime}", + "replication_num" = "1" + ); + """ + Awaitility.await().atMost(60, SECONDS).pollInterval(5, SECONDS).until( + { + try { + def restoreResult = sql """ + SELECT count(1) FROM ${s3table} + """ + println "restoreResult: ${restoreResult}" + return restoreResult.get(0).get(0) == 1 + } catch (Exception e) { + //tbl not found + return false + } + }) + + +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org