This is an automated email from the ASF dual-hosted git repository.
yuxia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss.git
The following commit(s) were added to refs/heads/main by this push:
new 2b1cce5a [filesystem] Add HuaweiCloud OBS integration (#1274)
2b1cce5a is described below
commit 2b1cce5aea6fe21907860cc8768fa43bd446a3a2
Author: andybj0228 <[email protected]>
AuthorDate: Wed Jul 9 14:04:56 2025 +0800
[filesystem] Add HuaweiCloud OBS integration (#1274)
---
fluss-dist/pom.xml | 7 +
fluss-dist/src/main/assemblies/plugins.xml | 6 +
fluss-filesystems/fluss-fs-obs/pom.xml | 259 +++++++++++++++++++++
.../com/alibaba/fluss/fs/obs/OBSFileSystem.java | 64 +++++
.../alibaba/fluss/fs/obs/OBSFileSystemPlugin.java | 133 +++++++++++
.../DynamicTemporaryOBSCredentialsProvider.java | 59 +++++
.../fs/obs/token/OBSSecurityTokenProvider.java | 111 +++++++++
.../fs/obs/token/OBSSecurityTokenReceiver.java | 108 +++++++++
.../src/main/resources/META-INF/NOTICE | 73 ++++++
.../resources/META-INF/licenses/LICENSE.jacoco | 14 ++
.../main/resources/META-INF/licenses/LICENSE.jaxb | 135 +++++++++++
.../main/resources/META-INF/licenses/LICENSE.jdom | 51 ++++
.../services/com.alibaba.fluss.fs.FileSystemPlugin | 17 ++
...om.alibaba.fluss.fs.token.SecurityTokenReceiver | 17 ++
.../fluss/fs/obs/OBSFileSystemBehaviorITCase.java | 64 +++++
.../alibaba/fluss/fs/obs/OBSTestCredentials.java | 118 ++++++++++
...redentialsProviderFileSystemBehaviorITCase.java | 65 ++++++
.../OBSWithTokenFileSystemBehaviorBaseITCase.java | 38 +++
.../obs/OBSWithTokenFileSystemBehaviorITCase.java | 72 ++++++
.../org.junit.jupiter.api.extension.Extension | 19 ++
fluss-filesystems/pom.xml | 1 +
fluss-test-coverage/pom.xml | 1 +
website/docs/maintenance/filesystems/obs.md | 62 +++++
website/docs/maintenance/filesystems/overview.md | 2 +
24 files changed, 1496 insertions(+)
diff --git a/fluss-dist/pom.xml b/fluss-dist/pom.xml
index 6a040abb..c9a710a0 100644
--- a/fluss-dist/pom.xml
+++ b/fluss-dist/pom.xml
@@ -68,6 +68,13 @@
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-fs-obs</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
<!-- metrics plugin -->
<dependency>
<groupId>com.alibaba.fluss</groupId>
diff --git a/fluss-dist/src/main/assemblies/plugins.xml
b/fluss-dist/src/main/assemblies/plugins.xml
index eee9b4f7..b610e5e2 100644
--- a/fluss-dist/src/main/assemblies/plugins.xml
+++ b/fluss-dist/src/main/assemblies/plugins.xml
@@ -45,6 +45,12 @@
<files>
<!-- filesystem -->
<!-- output directory should correspond to the file system *schema*
name, i.e., plugins/<schema>/ -->
+ <file>
+
<source>../fluss-filesystems/fluss-fs-obs/target/fluss-fs-obs-${project.version}.jar</source>
+ <outputDirectory>plugins/obs/</outputDirectory>
+ <destName>fluss-fs-obs-${project.version}.jar</destName>
+ <fileMode>0644</fileMode>
+ </file>
<file>
<source>../fluss-filesystems/fluss-fs-gs/target/fluss-fs-gs-${project.version}.jar</source>
<outputDirectory>plugins/gs/</outputDirectory>
diff --git a/fluss-filesystems/fluss-fs-obs/pom.xml
b/fluss-filesystems/fluss-fs-obs/pom.xml
new file mode 100644
index 00000000..b184532e
--- /dev/null
+++ b/fluss-filesystems/fluss-fs-obs/pom.xml
@@ -0,0 +1,259 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-filesystems</artifactId>
+ <version>0.8-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>fluss-fs-obs</artifactId>
+ <name>Fluss : FileSystems : OBS FS</name>
+
+ <properties>
+ <fs.obs.sdk.version>3.24.12</fs.obs.sdk.version>
+ <huaweicloud.sdk.iam.version>3.1.87</huaweicloud.sdk.iam.version>
+ <hadoop.huaweicloud.version>3.4.0</hadoop.huaweicloud.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-common</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-fs-hadoop-shaded</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-fs-hadoop</artifactId>
+ <version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-hdfs-client</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-huaweicloud</artifactId>
+ <version>${hadoop.huaweicloud.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>com.huaweicloud</groupId>
+ <artifactId>esdk-obs-java-bundle</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-common</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>ch.qos.reload4j</groupId>
+ <artifactId>reload4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-reload4j</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>com.huaweicloud</groupId>
+ <artifactId>esdk-obs-java</artifactId>
+ <version>${fs.obs.sdk.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.xml.bind</groupId>
+ <artifactId>jaxb-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>com.huaweicloud.sdk</groupId>
+ <artifactId>huaweicloud-sdk-iam</artifactId>
+ <version>${huaweicloud.sdk.iam.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk18on</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.openeuler</groupId>
+ <artifactId>bgmprovider</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <!-- Hadoop requires jaxb-api for javax.xml.bind.JAXBException -->
+ <groupId>javax.xml.bind</groupId>
+ <artifactId>jaxb-api</artifactId>
+ <version>${jaxb.api.version}</version>
+ <!-- packaged as an optional dependency that is only accessible on
Java 11+ -->
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-test-utils</artifactId>
+ </dependency>
+ <!-- for the behavior test suite -->
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-common</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <!-- jaxb-api is packaged as an optional
dependency that is only accessible on Java 11 -->
+ <Multi-Release>true</Multi-Release>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-javax-jars</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>javax.xml.bind</groupId>
+ <artifactId>jaxb-api</artifactId>
+ <version>${jaxb.api.version}</version>
+ <type>jar</type>
+ <overWrite>true</overWrite>
+ </artifactItem>
+ </artifactItems>
+
<outputDirectory>${project.build.directory}/temporary</outputDirectory>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>unpack-javax-libraries</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <target>
+ <echo message="unpacking javax jars"/>
+ <unzip
dest="${project.build.directory}/classes/META-INF/versions/11">
+ <fileset
dir="${project.build.directory}/temporary">
+ <include name="*"/>
+ </fileset>
+ </unzip>
+ </target>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>shade-fluss</id>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <artifactSet>
+ <includes>
+ <include>*:*</include>
+ </includes>
+ <excludes>
+
<exclude>javax.servlet:servlet-api</exclude>
+ <exclude>xmlenc:xmlenc</exclude>
+ </excludes>
+ </artifactSet>
+ <filters>
+ <filter>
+ <artifact>*</artifact>
+ <excludes>
+ <exclude>.gitkeep</exclude>
+ <exclude>mime.types</exclude>
+ <exclude>mozilla/**</exclude>
+ <exclude>LICENSE.txt</exclude>
+ <exclude>license/LICENSE*</exclude>
+
<exclude>okhttp3/internal/publicsuffix/NOTICE</exclude>
+ <exclude>NOTICE</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ <relocations>
+ <relocation>
+ <pattern>org.apache.commons</pattern>
+
<shadedPattern>com.alibaba.fluss.shaded.org.apache.commons</shadedPattern>
+ </relocation>
+ </relocations>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
diff --git
a/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/OBSFileSystem.java
b/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/OBSFileSystem.java
new file mode 100644
index 00000000..c6d4f1b8
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/OBSFileSystem.java
@@ -0,0 +1,64 @@
+/*
+ * 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 com.alibaba.fluss.fs.obs;
+
+import com.alibaba.fluss.fs.hdfs.HadoopFileSystem;
+import com.alibaba.fluss.fs.obs.token.OBSSecurityTokenProvider;
+import com.alibaba.fluss.fs.token.ObtainedSecurityToken;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+
+import java.io.IOException;
+
+/**
+ * A {@link FileSystem} for HuaweiCloud OBS that wraps an {@link
HadoopFileSystem}, but overwrite
+ * method to generate access security token.
+ */
+class OBSFileSystem extends HadoopFileSystem {
+
+ private final Configuration conf;
+ private volatile OBSSecurityTokenProvider obsSecurityTokenProvider;
+ private final String scheme;
+
+ OBSFileSystem(FileSystem hadoopFileSystem, String scheme, Configuration
conf) {
+ super(hadoopFileSystem);
+ this.scheme = scheme;
+ this.conf = conf;
+ }
+
+ @Override
+ public ObtainedSecurityToken obtainSecurityToken() throws IOException {
+ try {
+ mayCreateSecurityTokenProvider();
+ return obsSecurityTokenProvider.obtainSecurityToken(scheme);
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+
+ private void mayCreateSecurityTokenProvider() throws IOException {
+ if (obsSecurityTokenProvider == null) {
+ synchronized (this) {
+ if (obsSecurityTokenProvider == null) {
+ obsSecurityTokenProvider = new
OBSSecurityTokenProvider(conf);
+ }
+ }
+ }
+ }
+}
diff --git
a/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/OBSFileSystemPlugin.java
b/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/OBSFileSystemPlugin.java
new file mode 100644
index 00000000..c23e12e9
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/OBSFileSystemPlugin.java
@@ -0,0 +1,133 @@
+/*
+ * 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 com.alibaba.fluss.fs.obs;
+
+import com.alibaba.fluss.annotation.VisibleForTesting;
+import com.alibaba.fluss.config.ConfigBuilder;
+import com.alibaba.fluss.config.Configuration;
+import com.alibaba.fluss.fs.FileSystem;
+import com.alibaba.fluss.fs.FileSystemPlugin;
+import com.alibaba.fluss.fs.obs.token.OBSSecurityTokenReceiver;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URI;
+
+/** Simple factory for the HuaweiCloud OBS file system. */
+public class OBSFileSystemPlugin implements FileSystemPlugin {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(OBSFileSystemPlugin.class);
+
+ public static final String SCHEME = "obs";
+
+ /**
+ * In order to simplify, we make fluss obs configuration keys same with
hadoop obs module. So,
+ * we add all configuration key with prefix `fs.obs` in fluss conf to
hadoop conf
+ */
+ private static final String[] FLUSS_CONFIG_PREFIXES = {"fs.obs."};
+
+ private static final String ACCESS_KEY_ID = "fs.obs.access.key";
+ public static final String CREDENTIALS_PROVIDER =
"fs.obs.security.provider";
+
+ public static final String REGION_KEY = "fs.obs.region";
+
+ @Override
+ public String getScheme() {
+ return SCHEME;
+ }
+
+ @Override
+ public FileSystem create(URI fsUri, Configuration flussConfig) throws
IOException {
+ org.apache.hadoop.conf.Configuration hadoopConfig =
getHadoopConfiguration(flussConfig);
+
+ // set credential provider
+ if (hadoopConfig.get(ACCESS_KEY_ID) == null) {
+ String credentialsProvider =
hadoopConfig.get(CREDENTIALS_PROVIDER);
+ if (credentialsProvider != null) {
+ LOG.info(
+ "{} is not set, but {} is set, using credential
provider {}.",
+ ACCESS_KEY_ID,
+ CREDENTIALS_PROVIDER,
+ credentialsProvider);
+ } else {
+ // no ak, no credentialsProvider,
+ // set default credential provider which will get token from
+ // OBSSecurityTokenReceiver
+ setDefaultCredentialProvider(hadoopConfig);
+ }
+ } else {
+ LOG.info("{} is set, using provided access key id and secret.",
ACCESS_KEY_ID);
+ }
+
+ final String scheme = fsUri.getScheme();
+ final String authority = fsUri.getAuthority();
+
+ if (scheme == null && authority == null) {
+ fsUri =
org.apache.hadoop.fs.FileSystem.getDefaultUri(hadoopConfig);
+ } else if (scheme != null && authority == null) {
+ URI defaultUri =
org.apache.hadoop.fs.FileSystem.getDefaultUri(hadoopConfig);
+ if (scheme.equals(defaultUri.getScheme()) &&
defaultUri.getAuthority() != null) {
+ fsUri = defaultUri;
+ }
+ }
+
+ org.apache.hadoop.fs.FileSystem fileSystem = initFileSystem(fsUri,
hadoopConfig);
+ return new OBSFileSystem(fileSystem, getScheme(), hadoopConfig);
+ }
+
+ protected org.apache.hadoop.fs.FileSystem initFileSystem(
+ URI fsUri, org.apache.hadoop.conf.Configuration hadoopConfig)
throws IOException {
+ org.apache.hadoop.fs.obs.OBSFileSystem fileSystem =
+ new org.apache.hadoop.fs.obs.OBSFileSystem();
+ fileSystem.initialize(fsUri, hadoopConfig);
+ return fileSystem;
+ }
+
+ protected void
setDefaultCredentialProvider(org.apache.hadoop.conf.Configuration hadoopConfig)
{
+ // use OBSSecurityTokenReceiver to update hadoop config to set
credentialsProvider
+ OBSSecurityTokenReceiver.updateHadoopConfig(hadoopConfig);
+ }
+
+ @VisibleForTesting
+ org.apache.hadoop.conf.Configuration getHadoopConfiguration(Configuration
flussConfig) {
+ org.apache.hadoop.conf.Configuration conf = new
org.apache.hadoop.conf.Configuration();
+ if (flussConfig == null) {
+ return conf;
+ }
+
+ // read all configuration with prefix 'FLUSS_CONFIG_PREFIXES'
+ for (String key : flussConfig.keySet()) {
+ for (String prefix : FLUSS_CONFIG_PREFIXES) {
+ if (key.startsWith(prefix)) {
+ String value =
+ flussConfig.getString(
+
ConfigBuilder.key(key).stringType().noDefaultValue(), null);
+ conf.set(key, value);
+
+ LOG.debug(
+ "Adding Fluss config entry for {} as {} to Hadoop
config",
+ key,
+ conf.get(key));
+ }
+ }
+ }
+ return conf;
+ }
+}
diff --git
a/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/token/DynamicTemporaryOBSCredentialsProvider.java
b/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/token/DynamicTemporaryOBSCredentialsProvider.java
new file mode 100644
index 00000000..f690c384
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/token/DynamicTemporaryOBSCredentialsProvider.java
@@ -0,0 +1,59 @@
+/*
+ * 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 com.alibaba.fluss.fs.obs.token;
+
+import com.alibaba.fluss.annotation.Internal;
+
+import com.obs.services.IObsCredentialsProvider;
+import com.obs.services.internal.security.BasicSecurityKey;
+import com.obs.services.model.ISecurityKey;
+import org.apache.hadoop.fs.obs.OBSFileSystem;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Support dynamic session credentials for authenticating with HuaweiCloud
OBS. It'll get
+ * credentials from {@link OBSSecurityTokenReceiver}. It implements obs native
{@link
+ * IObsCredentialsProvider} to work with {@link OBSFileSystem}.
+ */
+@Internal
+public class DynamicTemporaryOBSCredentialsProvider implements
IObsCredentialsProvider {
+
+ private static final Logger LOG =
+
LoggerFactory.getLogger(DynamicTemporaryOBSCredentialsProvider.class);
+
+ public static final String NAME =
DynamicTemporaryOBSCredentialsProvider.class.getName();
+
+ @Override
+ public void setSecurityKey(ISecurityKey iSecurityKey) {
+ // do nothing
+ }
+
+ @Override
+ public ISecurityKey getSecurityKey() {
+ ISecurityKey credentials = OBSSecurityTokenReceiver.getCredentials();
+ if (credentials == null) {
+ throw new RuntimeException("Credentials is not ready.");
+ }
+ LOG.debug("Providing session credentials");
+ return new BasicSecurityKey(
+ credentials.getAccessKey(),
+ credentials.getSecretKey(),
+ credentials.getSecurityToken());
+ }
+}
diff --git
a/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/token/OBSSecurityTokenProvider.java
b/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/token/OBSSecurityTokenProvider.java
new file mode 100644
index 00000000..48b2724d
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/token/OBSSecurityTokenProvider.java
@@ -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.
+ */
+
+package com.alibaba.fluss.fs.obs.token;
+
+import com.alibaba.fluss.fs.token.Credentials;
+import com.alibaba.fluss.fs.token.CredentialsJsonSerde;
+import com.alibaba.fluss.fs.token.ObtainedSecurityToken;
+
+import com.huaweicloud.sdk.core.auth.GlobalCredentials;
+import com.huaweicloud.sdk.core.auth.ICredential;
+import com.huaweicloud.sdk.iam.v3.IamClient;
+import com.huaweicloud.sdk.iam.v3.model.CreateTemporaryAccessKeyByTokenRequest;
+import
com.huaweicloud.sdk.iam.v3.model.CreateTemporaryAccessKeyByTokenRequestBody;
+import
com.huaweicloud.sdk.iam.v3.model.CreateTemporaryAccessKeyByTokenResponse;
+import com.huaweicloud.sdk.iam.v3.model.Credential;
+import com.huaweicloud.sdk.iam.v3.model.IdentityToken;
+import com.huaweicloud.sdk.iam.v3.model.TokenAuth;
+import com.huaweicloud.sdk.iam.v3.model.TokenAuthIdentity;
+import com.huaweicloud.sdk.iam.v3.region.IamRegion;
+import org.apache.hadoop.conf.Configuration;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.alibaba.fluss.fs.obs.OBSFileSystemPlugin.REGION_KEY;
+
+/** A provider to provide HuaweiCloud obs security token. */
+public class OBSSecurityTokenProvider {
+
+ private static final String ACCESS_KEY_ID = "fs.obs.access.key";
+ private static final String ACCESS_KEY_SECRET = "fs.obs.secret.key";
+ private static final String ENDPOINT_KEY = "fs.obs.endpoint";
+
+ private final String endpoint;
+ private final String region;
+ private final IamClient iamClient;
+
+ public OBSSecurityTokenProvider(Configuration conf) {
+ endpoint = conf.get(ENDPOINT_KEY);
+ String accessKeyId = conf.get(ACCESS_KEY_ID);
+ String accessKeySecret = conf.get(ACCESS_KEY_SECRET);
+ region = conf.get(REGION_KEY);
+ // Create IAM client
+ ICredential credentials =
+ new
GlobalCredentials().withAk(accessKeyId).withSk(accessKeySecret);
+
+ iamClient =
+ IamClient.newBuilder()
+ .withCredential(credentials)
+ .withRegion(IamRegion.valueOf(region))
+ .build();
+ }
+
+ public ObtainedSecurityToken obtainSecurityToken(String scheme) {
+ final CreateTemporaryAccessKeyByTokenRequest request =
+ new CreateTemporaryAccessKeyByTokenRequest();
+ CreateTemporaryAccessKeyByTokenRequestBody body =
+ new CreateTemporaryAccessKeyByTokenRequestBody();
+ // todo: may consider make token duration time configurable, we don't
set it now
+ // token duration time is 1 hour by default
+ IdentityToken tokenIdentity = new IdentityToken();
+ tokenIdentity.withDurationSeconds(3600);
+ List<TokenAuthIdentity.MethodsEnum> listIdentityMethods = new
ArrayList<>();
+
listIdentityMethods.add(TokenAuthIdentity.MethodsEnum.fromValue("token"));
+ TokenAuthIdentity identityAuth = new TokenAuthIdentity();
+ identityAuth.withMethods(listIdentityMethods).withToken(tokenIdentity);
+ TokenAuth authbody = new TokenAuth();
+ authbody.withIdentity(identityAuth);
+ body.withAuth(authbody);
+ request.withBody(body);
+ final CreateTemporaryAccessKeyByTokenResponse response =
+ iamClient.createTemporaryAccessKeyByToken(request);
+ Credential credential = response.getCredential();
+ Map<String, String> additionInfo = new HashMap<>();
+ // we need to put endpoint as addition info
+ additionInfo.put(ENDPOINT_KEY, endpoint);
+ additionInfo.put(REGION_KEY, region);
+ return new ObtainedSecurityToken(
+ scheme,
+ toJson(credential),
+ Instant.parse(credential.getExpiresAt()).toEpochMilli(),
+ additionInfo);
+ }
+
+ private byte[] toJson(Credential credential) {
+ Credentials credentials =
+ new Credentials(
+ credential.getAccess(),
+ credential.getSecret(),
+ credential.getSecuritytoken());
+ return CredentialsJsonSerde.toJson(credentials);
+ }
+}
diff --git
a/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/token/OBSSecurityTokenReceiver.java
b/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/token/OBSSecurityTokenReceiver.java
new file mode 100644
index 00000000..8a56e50e
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/main/java/com/alibaba/fluss/fs/obs/token/OBSSecurityTokenReceiver.java
@@ -0,0 +1,108 @@
+/*
+ * 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 com.alibaba.fluss.fs.obs.token;
+
+import com.alibaba.fluss.fs.obs.OBSFileSystemPlugin;
+import com.alibaba.fluss.fs.token.CredentialsJsonSerde;
+import com.alibaba.fluss.fs.token.ObtainedSecurityToken;
+import com.alibaba.fluss.fs.token.SecurityTokenReceiver;
+
+import com.obs.services.internal.security.BasicSecurityKey;
+import com.obs.services.model.ISecurityKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+import static
com.alibaba.fluss.fs.obs.OBSFileSystemPlugin.CREDENTIALS_PROVIDER;
+
+/** Security token receiver for HuaweiCloud OBS filesystem. */
+public class OBSSecurityTokenReceiver implements SecurityTokenReceiver {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(OBSSecurityTokenReceiver.class);
+
+ static volatile ISecurityKey credentials;
+ static volatile Map<String, String> additionInfos;
+
+ public static void updateHadoopConfig(org.apache.hadoop.conf.Configuration
hadoopConfig) {
+ updateHadoopConfig(hadoopConfig,
DynamicTemporaryOBSCredentialsProvider.NAME);
+ }
+
+ protected static void updateHadoopConfig(
+ org.apache.hadoop.conf.Configuration hadoopConfig, String
credentialsProviderName) {
+ LOG.info("Updating Hadoop configuration");
+
+ String providers = hadoopConfig.get(CREDENTIALS_PROVIDER, "");
+
+ if (!providers.contains(credentialsProviderName)) {
+ if (providers.isEmpty()) {
+ LOG.debug("Setting provider");
+ providers = credentialsProviderName;
+ } else {
+ providers = credentialsProviderName + "," + providers;
+ LOG.debug("Prepending provider, new providers value: {}",
providers);
+ }
+ hadoopConfig.set(CREDENTIALS_PROVIDER, providers);
+ } else {
+ LOG.debug("Provider already exists");
+ }
+
+ // then, set addition info
+ if (additionInfos == null) {
+ // if addition info is null, it also means we have not received
any token,
+ throw new RuntimeException("Credentials is not ready.");
+ } else {
+ for (Map.Entry<String, String> entry : additionInfos.entrySet()) {
+ hadoopConfig.set(entry.getKey(), entry.getValue());
+ }
+ }
+
+ LOG.info("Updated Hadoop configuration successfully");
+ }
+
+ @Override
+ public String scheme() {
+ return OBSFileSystemPlugin.SCHEME;
+ }
+
+ @Override
+ public void onNewTokensObtained(ObtainedSecurityToken token) {
+ LOG.info("Updating session credentials");
+
+ byte[] tokenBytes = token.getToken();
+
+ com.alibaba.fluss.fs.token.Credentials flussCredentials =
+ CredentialsJsonSerde.fromJson(tokenBytes);
+
+ // Create Credential from fluss credentials
+ credentials =
+ new BasicSecurityKey(
+ flussCredentials.getAccessKeyId(),
+ flussCredentials.getSecretAccessKey(),
+ flussCredentials.getSecurityToken());
+ additionInfos = token.getAdditionInfos();
+
+ LOG.info(
+ "Session credentials updated successfully with access key:
{}.",
+ credentials.getAccessKey());
+ }
+
+ public static ISecurityKey getCredentials() {
+ return credentials;
+ }
+}
diff --git a/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/NOTICE
b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/NOTICE
new file mode 100644
index 00000000..767907e9
--- /dev/null
+++ b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,73 @@
+fluss-fs-obs
+Copyright 2025 The Apache Software Foundation
+
+This project includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+This project bundles the following dependencies under the Apache Software
License 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
+
+- com.huaweicloud:esdk-obs-java:3.24.12
+- com.huaweicloud.sdk:huaweicloud-sdk-iam:3.1.87
+- com.fasterxml.woodstox:woodstox-core:5.3.0
+- com.google.guava:guava:11.0.2
+- com.jamesmurty.utils:java-xmlbuilder:0.4
+- commons-beanutils:commons-beanutils:1.9.4
+- commons-collections:commons-collections:3.2.2
+- commons-configuration:commons-configuration:1.6
+- commons-digester:commons-digester:1.8
+- commons-io:commons-io:2.5
+- commons-lang:commons-lang:2.6
+- commons-logging:commons-logging:1.1.3
+- org.apache.hadoop:hadoop-huaweicloud:3.4.0
+- org.apache.httpcomponents:httpclient:4.5.13
+- org.apache.httpcomponents:httpcore:4.4.13
+- org.codehaus.jettison:jettison:1.5.4
+- org.ini4j:ini4j:0.5.4
+- net.java.dev.jets3t:jets3t:0.9.0
+- org.apache.commons:commons-compress:1.21
+- org.apache.commons:commons-lang3:3.12.0
+- org.apache.directory.api:api-asn1-api:1.0.0-M20
+- org.apache.directory.api:api-util:1.0.0-M20
+- org.apache.directory.server:apacheds-i18n:2.0.0-M15
+- org.apache.directory.server:apacheds-kerberos-codec:2.0.0-M15
+- org.apache.hadoop:hadoop-annotations:2.10.2
+- org.apache.hadoop:hadoop-auth:2.10.2
+- org.apache.hadoop:hadoop-common:2.10.2
+- org.apache.htrace:htrace-core4:4.1.0-incubating
+- org.codehaus.jackson:jackson-core-asl:1.9.13
+- org.codehaus.jackson:jackson-mapper-asl:1.9.13
+- org.codehaus.woodstox:stax2-api:4.2.1
+- org.mortbay.jetty:jetty-sslengine:6.1.26
+- org.mortbay.jetty:jetty-util:6.1.26
+- org.mortbay.jetty:jetty:6.1.26
+- com.fasterxml.jackson.core:jackson-annotations:2.15.3
+- com.fasterxml.jackson.core:jackson-core:2.15.3
+- com.fasterxml.jackson.core:jackson-databind:2.15.3
+- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.15.3
+- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.3
+- com.huaweicloud.sdk:huaweicloud-sdk-core:3.1.87
+- com.squareup.okhttp3:okhttp:4.12.0
+- com.squareup.okio:okio-jvm:3.8.0
+- com.squareup.okio:okio:3.8.0
+- org.apache.httpcomponents:httpcore:4.1.2
+- org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21
+- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21
+- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.21
+- org.jetbrains.kotlin:kotlin-stdlib:1.8.21
+- org.jetbrains:annotations:13.0
+- org.yaml:snakeyaml:2.0
+
+The binary distribution of this product bundles these dependencies under the
Eclipse Public License - v 2.0
(https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt)
+See bundled license files for details.
+
+- org.jacoco:org.jacoco.agent:runtime:0.8.5
+
+This project bundles the following dependencies under the JDOM license.
+See bundled license files for details.
+
+- org.jdom:jdom2:2.0.6.1
+
+This project bundles the following dependencies under the CDDL 1.1 license.
+See bundled license files for details.
+
+- javax.xml.bind:jaxb-api:2.3.1
diff --git
a/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/licenses/LICENSE.jacoco
b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/licenses/LICENSE.jacoco
new file mode 100644
index 00000000..47ff775b
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/licenses/LICENSE.jacoco
@@ -0,0 +1,14 @@
+License
+=======
+
+Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and Contributors
+
+The JaCoCo Java Code Coverage Library and all included documentation is made
+available by Mountainminds GmbH & Co. KG, Munich. Except indicated below, the
+Content is provided to you under the terms and conditions of the Eclipse Public
+License Version 2.0 ("EPL"). A copy of the EPL is available at
+[https://www.eclipse.org/legal/epl-2.0/](https://www.eclipse.org/legal/epl-2.0/).
+
+Please visit
+[http://www.jacoco.org/jacoco/trunk/doc/license.html](http://www.jacoco.org/jacoco/trunk/doc/license.html)
+for the complete license information including third party licenses and
trademarks.
\ No newline at end of file
diff --git
a/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/licenses/LICENSE.jaxb
b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/licenses/LICENSE.jaxb
new file mode 100644
index 00000000..fd16ea95
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/licenses/LICENSE.jaxb
@@ -0,0 +1,135 @@
+COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL)Version 1.1
+
+1. Definitions.
+
+ 1.1. "Contributor" means each individual or entity that creates or
contributes to the creation of Modifications.
+
+ 1.2. "Contributor Version" means the combination of the Original
Software, prior Modifications used by a Contributor (if any), and the
Modifications made by that particular Contributor.
+
+ 1.3. "Covered Software" means (a) the Original Software, or (b)
Modifications, or (c) the combination of files containing Original Software
with files containing Modifications, in each case including portions thereof.
+
+ 1.4. "Executable" means the Covered Software in any form other than
Source Code.
+
+ 1.5. "Initial Developer" means the individual or entity that first makes
Original Software available under this License.
+
+ 1.6. "Larger Work" means a work which combines Covered Software or
portions thereof with code not governed by the terms of this License.
+
+ 1.7. "License" means this document.
+
+ 1.8. "Licensable" means having the right to grant, to the maximum extent
possible, whether at the time of the initial grant or subsequently acquired,
any and all of the rights conveyed herein.
+
+ 1.9. "Modifications" means the Source Code and Executable form of any of
the following:
+
+ A. Any file that results from an addition to, deletion from or
modification of the contents of a file containing Original Software or previous
Modifications;
+
+ B. Any new file that contains any part of the Original Software or
previous Modification; or
+
+ C. Any new file that is contributed or otherwise made available under the
terms of this License.
+
+ 1.10. "Original Software" means the Source Code and Executable form of
computer software code that is originally released under this License.
+
+ 1.11. "Patent Claims" means any patent claim(s), now owned or hereafter
acquired, including without limitation, method, process, and apparatus claims,
in any patent Licensable by grantor.
+
+ 1.12. "Source Code" means (a) the common form of computer software code
in which modifications are made and (b) associated documentation included in or
with such code.
+
+ 1.13. "You" (or "Your") means an individual or a legal entity exercising
rights under, and complying with all of the terms of, this License. For legal
entities, "You" includes any entity which controls, is controlled by, or is
under common control with You. For purposes of this definition, "control" means
(a) the power, direct or indirect, to cause the direction or management of such
entity, whether by contract or otherwise, or (b) ownership of more than fifty
percent (50%) of the o [...]
+
+2. License Grants.
+
+ 2.1. The Initial Developer Grant.
+
+ Conditioned upon Your compliance with Section 3.1 below and subject to
third party intellectual property claims, the Initial Developer hereby grants
You a world-wide, royalty-free, non-exclusive license:
+
+ (a) under intellectual property rights (other than patent or trademark)
Licensable by Initial Developer, to use, reproduce, modify, display, perform,
sublicense and distribute the Original Software (or portions thereof), with or
without Modifications, and/or as part of a Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using or selling of
Original Software, to make, have made, use, practice, sell, and offer for sale,
and/or otherwise dispose of the Original Software (or portions thereof).
+
+ (c) The licenses granted in Sections 2.1(a) and (b) are effective on the
date Initial Developer first distributes or otherwise makes the Original
Software available to a third party under the terms of this License.
+
+ (d) Notwithstanding Section 2.1(b) above, no patent license is granted:
(1) for code that You delete from the Original Software, or (2) for
infringements caused by: (i) the modification of the Original Software, or (ii)
the combination of the Original Software with other software or devices.
+
+ 2.2. Contributor Grant.
+
+ Conditioned upon Your compliance with Section 3.1 below and subject to
third party intellectual property claims, each Contributor hereby grants You a
world-wide, royalty-free, non-exclusive license:
+
+ (a) under intellectual property rights (other than patent or trademark)
Licensable by Contributor to use, reproduce, modify, display, perform,
sublicense and distribute the Modifications created by such Contributor (or
portions thereof), either on an unmodified basis, with other Modifications, as
Covered Software and/or as part of a Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using, or selling of
Modifications made by that Contributor either alone and/or in combination with
its Contributor Version (or portions of such combination), to make, use, sell,
offer for sale, have made, and/or otherwise dispose of: (1) Modifications made
by that Contributor (or portions thereof); and (2) the combination of
Modifications made by that Contributor with its Contributor Version (or
portions of such combination).
+
+ (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on
the date Contributor first distributes or otherwise makes the Modifications
available to a third party.
+
+ (d) Notwithstanding Section 2.2(b) above, no patent license is granted:
(1) for any code that Contributor has deleted from the Contributor Version; (2)
for infringements caused by: (i) third party modifications of Contributor
Version, or (ii) the combination of Modifications made by that Contributor with
other software (except as part of the Contributor Version) or other devices; or
(3) under Patent Claims infringed by Covered Software in the absence of
Modifications made by that Co [...]
+
+3. Distribution Obligations.
+
+ 3.1. Availability of Source Code.
+
+ Any Covered Software that You distribute or otherwise make available in
Executable form must also be made available in Source Code form and that Source
Code form must be distributed only under the terms of this License. You must
include a copy of this License with every copy of the Source Code form of the
Covered Software You distribute or otherwise make available. You must inform
recipients of any such Covered Software in Executable form as to how they can
obtain such Covered Softw [...]
+
+ 3.2. Modifications.
+
+ The Modifications that You create or to which You contribute are governed
by the terms of this License. You represent that You believe Your Modifications
are Your original creation(s) and/or You have sufficient rights to grant the
rights conveyed by this License.
+
+ 3.3. Required Notices.
+
+ You must include a notice in each of Your Modifications that identifies
You as the Contributor of the Modification. You may not remove or alter any
copyright, patent or trademark notices contained within the Covered Software,
or any notices of licensing or any descriptive text giving attribution to any
Contributor or the Initial Developer.
+
+ 3.4. Application of Additional Terms.
+
+ You may not offer or impose any terms on any Covered Software in Source
Code form that alters or restricts the applicable version of this License or
the recipients' rights hereunder. You may choose to offer, and to charge a fee
for, warranty, support, indemnity or liability obligations to one or more
recipients of Covered Software. However, you may do so only on Your own behalf,
and not on behalf of the Initial Developer or any Contributor. You must make it
absolutely clear that any [...]
+
+ 3.5. Distribution of Executable Versions.
+
+ You may distribute the Executable form of the Covered Software under the
terms of this License or under the terms of a license of Your choice, which may
contain terms different from this License, provided that You are in compliance
with the terms of this License and that the license for the Executable form
does not attempt to limit or alter the recipient's rights in the Source Code
form from the rights set forth in this License. If You distribute the Covered
Software in Executable f [...]
+
+ 3.6. Larger Works.
+
+ You may create a Larger Work by combining Covered Software with other
code not governed by the terms of this License and distribute the Larger Work
as a single product. In such a case, You must make sure the requirements of
this License are fulfilled for the Covered Software.
+
+4. Versions of the License.
+
+ 4.1. New Versions.
+
+ Oracle is the initial license steward and may publish revised and/or new
versions of this License from time to time. Each version will be given a
distinguishing version number. Except as provided in Section 4.3, no one other
than the license steward has the right to modify this License.
+
+ 4.2. Effect of New Versions.
+
+ You may always continue to use, distribute or otherwise make the Covered
Software available under the terms of the version of the License under which
You originally received the Covered Software. If the Initial Developer includes
a notice in the Original Software prohibiting it from being distributed or
otherwise made available under any subsequent version of the License, You must
distribute and make the Covered Software available under the terms of the
version of the License under [...]
+
+ 4.3. Modified Versions.
+
+ When You are an Initial Developer and You want to create a new license
for Your Original Software, You may create and use a modified version of this
License if You: (a) rename the license and remove any references to the name of
the license steward (except to note that the license differs from this
License); and (b) otherwise make it clear that the license contains terms which
differ from this License.
+
+5. DISCLAIMER OF WARRANTY.
+
+ COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT
LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS,
MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK
AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD
ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL
DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUM [...]
+
+6. TERMINATION.
+
+ 6.1. This License and the rights granted hereunder will terminate
automatically if You fail to comply with terms herein and fail to cure such
breach within 30 days of becoming aware of the breach. Provisions which, by
their nature, must remain in effect beyond the termination of this License
shall survive.
+
+ 6.2. If You assert a patent infringement claim (excluding declaratory
judgment actions) against Initial Developer or a Contributor (the Initial
Developer or Contributor against whom You assert such claim is referred to as
"Participant") alleging that the Participant Software (meaning the Contributor
Version where the Participant is a Contributor or the Original Software where
the Participant is the Initial Developer) directly or indirectly infringes any
patent, then any and all righ [...]
+
+ 6.3. If You assert a patent infringement claim against Participant
alleging that the Participant Software directly or indirectly infringes any
patent where such claim is resolved (such as by license or settlement) prior to
the initiation of patent infringement litigation, then the reasonable value of
the licenses granted by such Participant under Sections 2.1 or 2.2 shall be
taken into account in determining the amount or value of any payment or license.
+
+ 6.4. In the event of termination under Sections 6.1 or 6.2 above, all end
user licenses that have been validly granted by You or any distributor
hereunder prior to termination (excluding licenses granted to You by any
distributor) shall survive termination.
+
+7. LIMITATION OF LIABILITY.
+
+ UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING
NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY
OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF
ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL,
INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
MALFUNCTION, OR ANY AND ALL OTHER COMM [...]
+
+8. U.S. GOVERNMENT END USERS.
+
+ The Covered Software is a "commercial item," as that term is defined in
48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer software" (as
that term is defined at 48 C.F.R. ? 252.227-7014(a)(1)) and "commercial
computer software documentation" as such terms are used in 48 C.F.R. 12.212
(Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through
227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Software
with only those rights set for [...]
+
+9. MISCELLANEOUS.
+
+ This License represents the complete agreement concerning subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. This License shall be governed by the law of the jurisdiction
specified in a notice contained within the Original Software (except to the
extent applicable law, if any, provides otherwise), excluding such
jurisdiction's conflict-of-law provisions. Any litiga [...]
+
+10. RESPONSIBILITY FOR CLAIMS.
+
+ As between Initial Developer and the Contributors, each party is
responsible for claims and damages arising, directly or indirectly, out of its
utilization of rights under this License and You agree to work with Initial
Developer and Contributors to distribute such responsibility on an equitable
basis. Nothing herein is intended or shall be deemed to constitute any
admission of liability.
+
+----------
+NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION
LICENSE (CDDL)
+The code released under the CDDL shall be governed by the laws of the State of
California (excluding conflict-of-law provisions). Any litigation relating to
this License shall be subject to the jurisdiction of the Federal Courts of the
Northern District of California and the state courts of the State of
California, with venue lying in Santa Clara County, California.
diff --git
a/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/licenses/LICENSE.jdom
b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/licenses/LICENSE.jdom
new file mode 100644
index 00000000..b420aeb0
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/licenses/LICENSE.jdom
@@ -0,0 +1,51 @@
+ Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
+ All rights reserved.
+
+This project bundles the following dependencies under the following license.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions, and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions, and the disclaimer that follows
+ these conditions in the documentation and/or other materials
+ provided with the distribution.
+
+ 3. The name "JDOM" must not be used to endorse or promote products
+ derived from this software without prior written permission. For
+ written permission, please contact <request_AT_jdom_DOT_org>.
+
+ 4. Products derived from this software may not be called "JDOM", nor
+ may "JDOM" appear in their name, without prior written permission
+ from the JDOM Project Management <request_AT_jdom_DOT_org>.
+
+ In addition, we request (but do not require) that you include in the
+ end-user documentation provided with the redistribution and/or in the
+ software itself an acknowledgement equivalent to the following:
+ "This product includes software developed by the
+ JDOM Project (http://www.jdom.org/)."
+ Alternatively, the acknowledgment may be graphical using the logos
+ available at http://www.jdom.org/images/logos.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+
+ This software consists of voluntary contributions made by many
+ individuals on behalf of the JDOM Project and was originally
+ created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
+ Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
+ on the JDOM Project, please see <http://www.jdom.org/>.
diff --git
a/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/services/com.alibaba.fluss.fs.FileSystemPlugin
b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/services/com.alibaba.fluss.fs.FileSystemPlugin
new file mode 100644
index 00000000..b7911eea
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/services/com.alibaba.fluss.fs.FileSystemPlugin
@@ -0,0 +1,17 @@
+# 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.
+
+com.alibaba.fluss.fs.obs.OBSFileSystemPlugin
diff --git
a/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/services/com.alibaba.fluss.fs.token.SecurityTokenReceiver
b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/services/com.alibaba.fluss.fs.token.SecurityTokenReceiver
new file mode 100644
index 00000000..90f6d232
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/main/resources/META-INF/services/com.alibaba.fluss.fs.token.SecurityTokenReceiver
@@ -0,0 +1,17 @@
+# 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.
+
+com.alibaba.fluss.fs.obs.token.OBSSecurityTokenReceiver
diff --git
a/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSFileSystemBehaviorITCase.java
b/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSFileSystemBehaviorITCase.java
new file mode 100644
index 00000000..85b439f9
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSFileSystemBehaviorITCase.java
@@ -0,0 +1,64 @@
+/*
+ * 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 com.alibaba.fluss.fs.obs;
+
+import com.alibaba.fluss.config.Configuration;
+import com.alibaba.fluss.fs.FileSystem;
+import com.alibaba.fluss.fs.FileSystemBehaviorTestSuite;
+import com.alibaba.fluss.fs.FsPath;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+
+import java.util.UUID;
+
+/**
+ * An implementation of the {@link FileSystemBehaviorTestSuite} for the OBS
file system with Hadoop
+ * obs sdk.
+ */
+class OBSFileSystemBehaviorITCase extends FileSystemBehaviorTestSuite {
+
+ private static final String TEST_DATA_DIR = "tests-" + UUID.randomUUID();
+
+ @BeforeAll
+ static void setup() {
+ OBSTestCredentials.assumeCredentialsAvailable();
+
+ final Configuration conf = new Configuration();
+ conf.setString("fs.obs.endpoint", OBSTestCredentials.getOBSEndpoint());
+ conf.setString("fs.obs.region", OBSTestCredentials.getOBSRegion());
+ conf.setString("fs.obs.access.key",
OBSTestCredentials.getOBSAccessKey());
+ conf.setString("fs.obs.secret.key",
OBSTestCredentials.getOBSSecretKey());
+ FileSystem.initialize(conf, null);
+ }
+
+ @Override
+ protected FileSystem getFileSystem() throws Exception {
+ return getBasePath().getFileSystem();
+ }
+
+ @Override
+ protected FsPath getBasePath() {
+ return new FsPath(OBSTestCredentials.getTestBucketUri() +
TEST_DATA_DIR);
+ }
+
+ @AfterAll
+ static void clearFsConfig() {
+ FileSystem.initialize(new Configuration(), null);
+ }
+}
diff --git
a/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSTestCredentials.java
b/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSTestCredentials.java
new file mode 100644
index 00000000..3193eb64
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSTestCredentials.java
@@ -0,0 +1,118 @@
+/*
+ * 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 com.alibaba.fluss.fs.obs;
+
+import javax.annotation.Nullable;
+
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+/** Access to credentials to access OBS buckets during integration tests. */
+public class OBSTestCredentials {
+ @Nullable private static final String ENDPOINT =
System.getenv("ARTIFACTS_OBS_ENDPOINT");
+
+ @Nullable private static final String REGION =
System.getenv("ARTIFACTS_OBS_REGION");
+
+ @Nullable private static final String BUCKET =
System.getenv("ARTIFACTS_OBS_BUCKET");
+
+ @Nullable private static final String ACCESS_KEY =
System.getenv("ARTIFACTS_OBS_ACCESS_KEY");
+
+ @Nullable private static final String SECRET_KEY =
System.getenv("ARTIFACTS_OBS_SECRET_KEY");
+
+ // ------------------------------------------------------------------------
+
+ public static boolean credentialsAvailable() {
+ return isNotEmpty(ENDPOINT)
+ && isNotEmpty(BUCKET)
+ && isNotEmpty(ACCESS_KEY)
+ && isNotEmpty(SECRET_KEY);
+ }
+
+ /** Checks if a String is not null and not empty. */
+ private static boolean isNotEmpty(@Nullable String str) {
+ return str != null && !str.isEmpty();
+ }
+
+ public static void assumeCredentialsAvailable() {
+ assumeTrue(
+ credentialsAvailable(), "No OBS credentials available in this
test's environment");
+ }
+
+ /**
+ * Get OBS endpoint used to connect.
+ *
+ * @return OBS endpoint
+ */
+ public static String getOBSEndpoint() {
+ if (ENDPOINT != null) {
+ return ENDPOINT;
+ } else {
+ throw new IllegalStateException("OBS endpoint is not available");
+ }
+ }
+
+ /**
+ * Get the region for the OBS.
+ *
+ * @return OBS region
+ */
+ public static String getOBSRegion() {
+ if (REGION != null) {
+ return REGION;
+ } else {
+ throw new IllegalStateException("OBS region is not available");
+ }
+ }
+
+ /**
+ * Get OBS access key.
+ *
+ * @return OBS access key
+ */
+ public static String getOBSAccessKey() {
+ if (ACCESS_KEY != null) {
+ return ACCESS_KEY;
+ } else {
+ throw new IllegalStateException("OBS access key is not available");
+ }
+ }
+
+ /**
+ * Get OBS secret key.
+ *
+ * @return OBS secret key
+ */
+ public static String getOBSSecretKey() {
+ if (SECRET_KEY != null) {
+ return SECRET_KEY;
+ } else {
+ throw new IllegalStateException("OBS secret key is not available");
+ }
+ }
+
+ public static String getTestBucketUri() {
+ return getTestBucketUriWithScheme("obs");
+ }
+
+ public static String getTestBucketUriWithScheme(String scheme) {
+ if (BUCKET != null) {
+ return scheme + "://" + BUCKET + "/";
+ } else {
+ throw new IllegalStateException("OBS test bucket is not
available");
+ }
+ }
+}
diff --git
a/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSWithCredentialsProviderFileSystemBehaviorITCase.java
b/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSWithCredentialsProviderFileSystemBehaviorITCase.java
new file mode 100644
index 00000000..411e8532
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSWithCredentialsProviderFileSystemBehaviorITCase.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.alibaba.fluss.fs.obs;
+
+import com.alibaba.fluss.config.Configuration;
+import com.alibaba.fluss.fs.FileSystem;
+import com.alibaba.fluss.fs.FileSystemBehaviorTestSuite;
+import com.alibaba.fluss.fs.FsPath;
+
+import com.obs.services.EnvironmentVariableObsCredentialsProvider;
+import com.obs.services.IObsCredentialsProvider;
+import org.junit.jupiter.api.BeforeAll;
+
+import java.util.UUID;
+
+import static
com.alibaba.fluss.fs.obs.OBSFileSystemPlugin.CREDENTIALS_PROVIDER;
+
+/** IT case for access obs via set {@link IObsCredentialsProvider}. */
+class OBSWithCredentialsProviderFileSystemBehaviorITCase extends
FileSystemBehaviorTestSuite {
+
+ private static final String TEST_DATA_DIR = "tests-" + UUID.randomUUID();
+
+ @BeforeAll
+ static void setup() {
+ OBSTestCredentials.assumeCredentialsAvailable();
+
+ // use EnvironmentVariableObsCredentialsProvider
+ final Configuration conf = new Configuration();
+ conf.setString(
+ CREDENTIALS_PROVIDER,
+
EnvironmentVariableObsCredentialsProvider.class.getCanonicalName());
+ conf.setString("fs.obs.endpoint", OBSTestCredentials.getOBSEndpoint());
+ conf.setString("fs.obs.region", OBSTestCredentials.getOBSRegion());
+
+ // now, we need to set oss config to system properties
+ System.setProperty("obs.access.key",
OBSTestCredentials.getOBSAccessKey());
+ System.setProperty("obs.secret.key",
OBSTestCredentials.getOBSSecretKey());
+ FileSystem.initialize(conf, null);
+ }
+
+ @Override
+ protected FileSystem getFileSystem() throws Exception {
+ return getBasePath().getFileSystem();
+ }
+
+ @Override
+ protected FsPath getBasePath() {
+ return new FsPath(OBSTestCredentials.getTestBucketUri() +
TEST_DATA_DIR);
+ }
+}
diff --git
a/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSWithTokenFileSystemBehaviorBaseITCase.java
b/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSWithTokenFileSystemBehaviorBaseITCase.java
new file mode 100644
index 00000000..a2d8705d
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSWithTokenFileSystemBehaviorBaseITCase.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.alibaba.fluss.fs.obs;
+
+import com.alibaba.fluss.config.Configuration;
+import com.alibaba.fluss.fs.FileSystem;
+import com.alibaba.fluss.fs.FileSystemBehaviorTestSuite;
+
+/** Base IT case for access obs with temporary credentials in hadoop sdk as
OBS FileSystem. */
+abstract class OBSWithTokenFileSystemBehaviorBaseITCase extends
FileSystemBehaviorTestSuite {
+
+ static void initFileSystemWithSecretKey() {
+ OBSTestCredentials.assumeCredentialsAvailable();
+
+ // first init filesystem with ak/sk
+ final Configuration conf = new Configuration();
+ conf.setString("fs.obs.endpoint", OBSTestCredentials.getOBSEndpoint());
+ conf.setString("fs.obs.region", OBSTestCredentials.getOBSRegion());
+ conf.setString("fs.obs.access.key",
OBSTestCredentials.getOBSAccessKey());
+ conf.setString("fs.obs.secret.key",
OBSTestCredentials.getOBSSecretKey());
+ FileSystem.initialize(conf, null);
+ }
+}
diff --git
a/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSWithTokenFileSystemBehaviorITCase.java
b/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSWithTokenFileSystemBehaviorITCase.java
new file mode 100644
index 00000000..ae45379b
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/test/java/com/alibaba/fluss/fs/obs/OBSWithTokenFileSystemBehaviorITCase.java
@@ -0,0 +1,72 @@
+/*
+ * 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 com.alibaba.fluss.fs.obs;
+
+import com.alibaba.fluss.config.Configuration;
+import com.alibaba.fluss.fs.FileSystem;
+import com.alibaba.fluss.fs.FsPath;
+import com.alibaba.fluss.fs.obs.token.OBSSecurityTokenReceiver;
+import com.alibaba.fluss.fs.token.ObtainedSecurityToken;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+
+import java.util.UUID;
+
+/** IT case for access obs with iam token in hadoop sdk as FileSystem. */
+class OBSWithTokenFileSystemBehaviorITCase extends
OBSWithTokenFileSystemBehaviorBaseITCase {
+
+ private static final String TEST_DATA_DIR = "tests-" + UUID.randomUUID();
+
+ @BeforeAll
+ static void setup() throws Exception {
+ // init a filesystem with ak/sk so that it can generate iam token
+ initFileSystemWithSecretKey();
+ // now, we can init with iam token
+ initFileSystemWithToken(getFsPath());
+ }
+
+ @Override
+ protected FileSystem getFileSystem() throws Exception {
+ return getFsPath().getFileSystem();
+ }
+
+ @Override
+ protected FsPath getBasePath() {
+ return getFsPath();
+ }
+
+ protected static FsPath getFsPath() {
+ return new FsPath(OBSTestCredentials.getTestBucketUri() +
TEST_DATA_DIR);
+ }
+
+ @AfterAll
+ static void clearFsConfig() {
+ FileSystem.initialize(new Configuration(), null);
+ }
+
+ private static void initFileSystemWithToken(FsPath fsPath) throws
Exception {
+ Configuration configuration = new Configuration();
+ // obtain a security token and call onNewTokensObtained
+ ObtainedSecurityToken obtainedSecurityToken =
fsPath.getFileSystem().obtainSecurityToken();
+ OBSSecurityTokenReceiver obsSecurityTokenReceiver = new
OBSSecurityTokenReceiver();
+ obsSecurityTokenReceiver.onNewTokensObtained(obtainedSecurityToken);
+
+ FileSystem.initialize(configuration, null);
+ }
+}
diff --git
a/fluss-filesystems/fluss-fs-obs/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
b/fluss-filesystems/fluss-fs-obs/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
new file mode 100644
index 00000000..590243ea
--- /dev/null
+++
b/fluss-filesystems/fluss-fs-obs/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+com.alibaba.fluss.testutils.common.TestLoggerExtension
\ No newline at end of file
diff --git a/fluss-filesystems/pom.xml b/fluss-filesystems/pom.xml
index 18f52640..6b6194c4 100644
--- a/fluss-filesystems/pom.xml
+++ b/fluss-filesystems/pom.xml
@@ -34,6 +34,7 @@
<module>fluss-fs-oss</module>
<module>fluss-fs-s3</module>
<module>fluss-fs-gs</module>
+ <module>fluss-fs-obs</module>
</modules>
<packaging>pom</packaging>
diff --git a/fluss-test-coverage/pom.xml b/fluss-test-coverage/pom.xml
index 54d00d30..20d8d2c8 100644
--- a/fluss-test-coverage/pom.xml
+++ b/fluss-test-coverage/pom.xml
@@ -306,6 +306,7 @@
<exclude>com.alibaba.fluss.fs.hdfs.HadoopSecurityTokenReceiver</exclude>
<exclude>com.alibaba.fluss.fs.oss.*</exclude>
<exclude>com.alibaba.fluss.fs.s3.*</exclude>
+
<exclude>com.alibaba.fluss.fs.obs.*</exclude>
<exclude>com.amazonaws.services.s3.model.transform.XmlResponsesSaxParser*</exclude>
<exclude>com.alibaba.fluss.rocksdb.RocksIteratorWrapper
</exclude>
diff --git a/website/docs/maintenance/filesystems/obs.md
b/website/docs/maintenance/filesystems/obs.md
new file mode 100644
index 00000000..74d65b90
--- /dev/null
+++ b/website/docs/maintenance/filesystems/obs.md
@@ -0,0 +1,62 @@
+---
+title: HuaweiCloud OBS
+sidebar_position: 6
+---
+
+<!--
+ 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.
+-->
+
+# HuaweiCloud OBS
+
+[HuaweiCloud Object Storage
Service](https://www.huaweicloud.com/product/obs.html) (HuaweiCloud OBS) is an
enterprise-grade object storage solution delivering industry-leading
scalability, data durability, security, and cost-efficiency. Trusted by
organizations across finance, healthcare, manufacturing, and media, OBS enables
you to securely store, manage, analyze, and protect unlimited data volumes for
diverse scenarios like AI training, data lakes, multi-cloud backup, and
real-time med [...]
+
+## Configurations setup
+
+To enabled HuaweiCloud OBS as remote storage, there are some required
configurations that must be added to Fluss' `server.yaml`:
+
+```yaml
+# The dir that used to be as the remote storage of Fluss
+remote.data.dir: obs://<your-bucket>/path/to/remote/storage
+# obs endpoint, such as: https://obs.cn-north-4.myhuaweicloud.com
+fs.obs.endpoint: <obs-endpoint-hostname>
+# OBS region, such as: cn-north-4
+fs.obs.region: <your-obs-region>
+
+# Authentication (choose one option below)
+
+# Option 1: Direct credentials
+# HuaweiCloud access key
+fs.obs.access.key: <your-access-key>
+# HuaweiCloud secret key
+fs.obs.secret.key: <your-secret-key>
+
+# Option 2: Secure credential provider
+fs.obs.security.provider: <your-credentials-provider>
+```
+To avoid exposing sensitive access key information directly in the
`server.yaml`, you can choose option2 to use a credential provider by setting
the `fs.obs.security.provider` property.
+
+For example, to use environment variables for credential management:
+```yaml
+fs.obs.security.provider:
com.obs.services.EnvironmentVariableObsCredentialsProvider
+```
+Then, set the following environment variables before starting the Fluss
service:
+```bash
+export OBS_ACCESS_KEY_ID=<your-access-key>
+export OBS_SECRET_ACCESS_KEY=<your-secret-key>
+```
+This approach enhances security by keeping sensitive credentials out of
configuration files.
\ No newline at end of file
diff --git a/website/docs/maintenance/filesystems/overview.md
b/website/docs/maintenance/filesystems/overview.md
index 18cf09e9..f14a7afc 100644
--- a/website/docs/maintenance/filesystems/overview.md
+++ b/website/docs/maintenance/filesystems/overview.md
@@ -54,4 +54,6 @@ The Fluss project supports the following file system:
- **[AWS S3](s3.md)** is supported by `fluss-fs-s3` and registered under the
`s3://` URI scheme.
+- **[HuaweiCloud OBS](obs.md)** is supported by `fluss-fs-obs` and registered
under the `obs://` URI scheme.
+
The implementation is based on [Hadoop Project](https://hadoop.apache.org/)
but is self-contained with no dependency footprint.
\ No newline at end of file