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

morrysnow pushed a commit to branch branch-3.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-3.1 by this push:
     new 869f94cca39 branch-3.1: [feat](params-refactor) restructure parameters 
for FS V2 integration #51177 (#53180)
869f94cca39 is described below

commit 869f94cca39675f8447a2b298eacbc4bac03e20a
Author: Calvin Kirs <[email protected]>
AuthorDate: Tue Jul 15 13:33:07 2025 +0800

    branch-3.1: [feat](params-refactor) restructure parameters for FS V2 
integration #51177 (#53180)
    
    pickd from #51177
---
 .../org/apache/doris/common/util/LocationPath.java |   4 +-
 .../doris/datasource/ExternalMetaCacheMgr.java     |   2 +-
 .../doris/datasource/hive/HMSExternalCatalog.java  |   6 +-
 .../doris/datasource/hive/HMSTransaction.java      |  12 +-
 .../doris/datasource/hive/HiveMetaStoreCache.java  |   8 +-
 .../org/apache/doris/datasource/hive/HiveUtil.java |   4 +-
 .../doris/fs/remote/SwitchingFileSystem.java       |  14 +-
 .../DirectoryLister.java}                          |  25 +--
 .../org/apache/doris/fsv2/FileSystemCache.java     | 117 +++++++++++
 .../FileSystemDirectoryLister.java}                |  29 +--
 .../org/apache/doris/fsv2/FileSystemFactory.java   |  17 ++
 .../apache/doris/fsv2/FileSystemIOException.java   |  65 ++++++
 .../FileSystemProvider.java}                       |  20 +-
 .../apache/doris/fsv2/FileSystemProviderImpl.java  |  43 ++++
 .../java/org/apache/doris/fsv2/FileSystemType.java |  49 +++++
 .../java/org/apache/doris/fsv2/FileSystemUtil.java |  70 +++++++
 .../org/apache/doris/fsv2/LocalDfsFileSystem.java  | 199 +++++++++++++++++++
 .../doris/fsv2/RemoteFileRemoteIterator.java       |  47 +++++
 .../RemoteFiles.java}                              |  21 +-
 .../RemoteIterator.java}                           |  23 +--
 .../SimpleRemoteIterator.java}                     |  31 +--
 .../fsv2/TransactionDirectoryListingCacheKey.java  |  64 ++++++
 .../TransactionScopeCachingDirectoryLister.java    | 219 +++++++++++++++++++++
 .../{fs => fsv2}/remote/SwitchingFileSystem.java   |   8 +-
 .../doris/transaction/HiveTransactionManager.java  |   2 +-
 .../transaction/TransactionManagerFactory.java     |   2 +-
 .../apache/doris/common/util/LocationPathTest.java |  10 +-
 .../doris/datasource/hive/HmsCommitTest.java       |   8 +-
 .../doris/fs/remote/RemoteFileSystemTest.java      | 158 ---------------
 .../doris/fsv2/remote/RemoteFileSystemTest.java    | 172 ++++++++++++++++
 30 files changed, 1169 insertions(+), 280 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/common/util/LocationPath.java 
b/fe/fe-core/src/main/java/org/apache/doris/common/util/LocationPath.java
index 798e13d321f..9374f2ccca1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/util/LocationPath.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/LocationPath.java
@@ -26,7 +26,7 @@ import 
org.apache.doris.datasource.property.constants.CosProperties;
 import org.apache.doris.datasource.property.constants.ObsProperties;
 import org.apache.doris.datasource.property.constants.OssProperties;
 import org.apache.doris.datasource.property.constants.S3Properties;
-import org.apache.doris.fs.FileSystemType;
+import org.apache.doris.fsv2.FileSystemType;
 import org.apache.doris.thrift.TFileType;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -453,7 +453,7 @@ public class LocationPath {
             case OSS_HDFS: // if hdfs service is enabled on oss, use hdfs lib 
to access oss.
             case VIEWFS:
             case GFS:
-                fsType = FileSystemType.DFS;
+                fsType = FileSystemType.HDFS;
                 break;
             case JFS:
                 fsType = FileSystemType.JFS;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalMetaCacheMgr.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalMetaCacheMgr.java
index e777285a07f..cb1b74faa49 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalMetaCacheMgr.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalMetaCacheMgr.java
@@ -37,7 +37,7 @@ import org.apache.doris.datasource.metacache.MetaCache;
 import org.apache.doris.datasource.mvcc.MvccUtil;
 import org.apache.doris.datasource.paimon.PaimonMetadataCache;
 import org.apache.doris.datasource.paimon.PaimonMetadataCacheMgr;
-import org.apache.doris.fs.FileSystemCache;
+import org.apache.doris.fsv2.FileSystemCache;
 import org.apache.doris.nereids.exceptions.NotSupportedException;
 
 import com.github.benmanes.caffeine.cache.CacheLoader;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSExternalCatalog.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSExternalCatalog.java
index 90636495335..f5ba58c5763 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSExternalCatalog.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSExternalCatalog.java
@@ -37,9 +37,9 @@ import 
org.apache.doris.datasource.jdbc.client.JdbcClientConfig;
 import org.apache.doris.datasource.operations.ExternalMetadataOperations;
 import org.apache.doris.datasource.property.PropertyConverter;
 import org.apache.doris.datasource.property.constants.HMSProperties;
-import org.apache.doris.fs.FileSystemProvider;
-import org.apache.doris.fs.FileSystemProviderImpl;
-import org.apache.doris.fs.remote.dfs.DFSFileSystem;
+import org.apache.doris.fsv2.FileSystemProvider;
+import org.apache.doris.fsv2.FileSystemProviderImpl;
+import org.apache.doris.fsv2.remote.dfs.DFSFileSystem;
 import org.apache.doris.transaction.TransactionManagerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSTransaction.java 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSTransaction.java
index dd29204bccc..d49d60ea7f7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSTransaction.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSTransaction.java
@@ -27,12 +27,12 @@ import org.apache.doris.common.UserException;
 import org.apache.doris.common.profile.SummaryProfile;
 import org.apache.doris.datasource.NameMapping;
 import org.apache.doris.datasource.statistics.CommonStatistics;
-import org.apache.doris.fs.FileSystem;
-import org.apache.doris.fs.FileSystemProvider;
-import org.apache.doris.fs.FileSystemUtil;
-import org.apache.doris.fs.remote.RemoteFile;
-import org.apache.doris.fs.remote.S3FileSystem;
-import org.apache.doris.fs.remote.SwitchingFileSystem;
+import org.apache.doris.fsv2.FileSystem;
+import org.apache.doris.fsv2.FileSystemProvider;
+import org.apache.doris.fsv2.FileSystemUtil;
+import org.apache.doris.fsv2.remote.RemoteFile;
+import org.apache.doris.fsv2.remote.S3FileSystem;
+import org.apache.doris.fsv2.remote.SwitchingFileSystem;
 import 
org.apache.doris.nereids.trees.plans.commands.insert.HiveInsertCommandContext;
 import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.thrift.TFileType;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveMetaStoreCache.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveMetaStoreCache.java
index ad27cd62384..37631685a0e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveMetaStoreCache.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveMetaStoreCache.java
@@ -46,10 +46,10 @@ import 
org.apache.doris.datasource.hive.HiveUtil.ACIDFileFilter;
 import org.apache.doris.datasource.hive.HiveUtil.FullAcidFileFilter;
 import org.apache.doris.datasource.hive.HiveUtil.InsertOnlyACIDFileFilter;
 import org.apache.doris.datasource.property.PropertyConverter;
-import org.apache.doris.fs.FileSystemCache;
-import org.apache.doris.fs.remote.RemoteFile;
-import org.apache.doris.fs.remote.RemoteFileSystem;
-import org.apache.doris.fs.remote.dfs.DFSFileSystem;
+import org.apache.doris.fsv2.FileSystemCache;
+import org.apache.doris.fsv2.remote.RemoteFile;
+import org.apache.doris.fsv2.remote.RemoteFileSystem;
+import org.apache.doris.fsv2.remote.dfs.DFSFileSystem;
 import org.apache.doris.metric.GaugeMetric;
 import org.apache.doris.metric.Metric;
 import org.apache.doris.metric.MetricLabel;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveUtil.java 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveUtil.java
index 26c62ba1e0a..c9a8c948477 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveUtil.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveUtil.java
@@ -22,8 +22,8 @@ import org.apache.doris.common.Pair;
 import org.apache.doris.common.UserException;
 import org.apache.doris.datasource.ExternalCatalog;
 import org.apache.doris.datasource.statistics.CommonStatistics;
-import org.apache.doris.fs.remote.BrokerFileSystem;
-import org.apache.doris.fs.remote.RemoteFileSystem;
+import org.apache.doris.fsv2.remote.BrokerFileSystem;
+import org.apache.doris.fsv2.remote.RemoteFileSystem;
 import org.apache.doris.nereids.exceptions.AnalysisException;
 import org.apache.doris.qe.ConnectContext;
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/fs/remote/SwitchingFileSystem.java 
b/fe/fe-core/src/main/java/org/apache/doris/fs/remote/SwitchingFileSystem.java
index ab7c91d693a..7b57f4d76d7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/fs/remote/SwitchingFileSystem.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/fs/remote/SwitchingFileSystem.java
@@ -18,10 +18,8 @@
 package org.apache.doris.fs.remote;
 
 import org.apache.doris.backup.Status;
-import org.apache.doris.common.util.LocationPath;
 import org.apache.doris.datasource.ExternalMetaCacheMgr;
 import org.apache.doris.fs.FileSystem;
-import org.apache.doris.fs.FileSystemCache;
 
 import java.util.List;
 import java.util.Map;
@@ -36,7 +34,7 @@ public class SwitchingFileSystem implements FileSystem {
     private final Map<String, String> properties;
 
     public SwitchingFileSystem(ExternalMetaCacheMgr extMetaCacheMgr, String 
bindBrokerName,
-            Map<String, String> properties) {
+                               Map<String, String> properties) {
         this.extMetaCacheMgr = extMetaCacheMgr;
         this.bindBrokerName = bindBrokerName;
         this.properties = properties;
@@ -123,10 +121,16 @@ public class SwitchingFileSystem implements FileSystem {
     }
 
     public FileSystem fileSystem(String location) {
-        return extMetaCacheMgr.getFsCache().getRemoteFileSystem(
+        // todo: This method is currently unused.
+        // LocationPath has already been adapted to the new V2 logic.
+        // We’re keeping this code commented out for now, but it will be fully 
removed once
+        // V2 is finalized and fully adopted.
+        /* return extMetaCacheMgr.getFsCache().getRemoteFileSystem(
                 new FileSystemCache.FileSystemCacheKey(
                         LocationPath.getFSIdentity(location, properties,
-                                bindBrokerName), properties, bindBrokerName));
+                                bindBrokerName), properties, 
bindBrokerName));*/
+        //
+        return null;
     }
 }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
 b/fe/fe-core/src/main/java/org/apache/doris/fsv2/DirectoryLister.java
similarity index 53%
copy from 
fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
copy to fe/fe-core/src/main/java/org/apache/doris/fsv2/DirectoryLister.java
index b8898d9b279..7440d15166e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/DirectoryLister.java
@@ -14,23 +14,16 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+// This file is copied from
+// 
https://github.com/trinodb/trino/blob/438/plugin/trino-hive/src/main/java/io/trino/plugin/hive/fs/DirectoryLister.java
+// and modified by Doris
 
-package org.apache.doris.transaction;
+package org.apache.doris.fsv2;
 
-import org.apache.doris.datasource.hive.HiveMetadataOps;
-import org.apache.doris.datasource.iceberg.IcebergMetadataOps;
-import org.apache.doris.fs.FileSystemProvider;
+import org.apache.doris.catalog.TableIf;
+import org.apache.doris.fsv2.remote.RemoteFile;
 
-import java.util.concurrent.Executor;
-
-public class TransactionManagerFactory {
-
-    public static TransactionManager 
createHiveTransactionManager(HiveMetadataOps ops,
-            FileSystemProvider fileSystemProvider, Executor 
fileSystemExecutor) {
-        return new HiveTransactionManager(ops, fileSystemProvider, 
fileSystemExecutor);
-    }
-
-    public static TransactionManager 
createIcebergTransactionManager(IcebergMetadataOps ops) {
-        return new IcebergTransactionManager(ops);
-    }
+public interface DirectoryLister {
+    RemoteIterator<RemoteFile> listFiles(FileSystem fs, boolean recursive, 
TableIf table, String location)
+            throws FileSystemIOException;
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemCache.java 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemCache.java
new file mode 100644
index 00000000000..578f76b8275
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemCache.java
@@ -0,0 +1,117 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.fsv2;
+
+import org.apache.doris.common.CacheFactory;
+import org.apache.doris.common.Config;
+import org.apache.doris.common.Pair;
+import org.apache.doris.common.UserException;
+import org.apache.doris.fsv2.remote.RemoteFileSystem;
+
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import org.apache.hadoop.conf.Configuration;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.OptionalLong;
+
+public class FileSystemCache {
+
+    private final LoadingCache<FileSystemCacheKey, RemoteFileSystem> 
fileSystemCache;
+
+    public FileSystemCache() {
+        // no need to set refreshAfterWrite, because the FileSystem is created 
once and never changed
+        CacheFactory fsCacheFactory = new CacheFactory(
+                OptionalLong.of(86400L),
+                OptionalLong.empty(),
+                Config.max_remote_file_system_cache_num,
+                false,
+                null);
+        fileSystemCache = fsCacheFactory.buildCache(this::loadFileSystem);
+    }
+
+    private RemoteFileSystem loadFileSystem(FileSystemCacheKey key) throws 
UserException {
+        return FileSystemFactory.get(key.type, key.getFsProperties(), 
key.bindBrokerName);
+    }
+
+    public RemoteFileSystem getRemoteFileSystem(FileSystemCacheKey key) {
+        return fileSystemCache.get(key);
+    }
+
+    public static class FileSystemCacheKey {
+        private final FileSystemType type;
+        // eg: hdfs://nameservices1
+        private final String fsIdent;
+        private final Map<String, String> properties;
+        private final String bindBrokerName;
+        // only for creating new file system
+        private final Configuration conf;
+
+        public FileSystemCacheKey(Pair<FileSystemType, String> fs,
+                                  Map<String, String> properties,
+                                  String bindBrokerName,
+                                  Configuration conf) {
+            this.type = fs.first;
+            this.fsIdent = fs.second;
+            this.properties = properties;
+            this.bindBrokerName = bindBrokerName;
+            this.conf = conf;
+        }
+
+        public FileSystemCacheKey(Pair<FileSystemType, String> fs,
+                                  Map<String, String> properties, String 
bindBrokerName) {
+            this(fs, properties, bindBrokerName, null);
+        }
+
+        public Map<String, String> getFsProperties() {
+            if (conf == null) {
+                return properties;
+            }
+            Map<String, String> result = new HashMap<>();
+            conf.iterator().forEachRemaining(e -> result.put(e.getKey(), 
e.getValue()));
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof FileSystemCacheKey)) {
+                return false;
+            }
+            FileSystemCacheKey o = (FileSystemCacheKey) obj;
+            boolean equalsWithoutBroker = type.equals(o.type)
+                    && fsIdent.equals(o.fsIdent)
+                    && properties.equals(o.properties);
+            if (bindBrokerName == null) {
+                return equalsWithoutBroker && o.bindBrokerName == null;
+            }
+            return equalsWithoutBroker && 
bindBrokerName.equals(o.bindBrokerName);
+        }
+
+        @Override
+        public int hashCode() {
+            if (bindBrokerName == null) {
+                return Objects.hash(properties, fsIdent, type);
+            }
+            return Objects.hash(properties, fsIdent, type, bindBrokerName);
+        }
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
 b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemDirectoryLister.java
similarity index 52%
copy from 
fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
copy to 
fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemDirectoryLister.java
index b8898d9b279..6fe1b110783 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemDirectoryLister.java
@@ -15,22 +15,23 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package org.apache.doris.transaction;
+package org.apache.doris.fsv2;
 
-import org.apache.doris.datasource.hive.HiveMetadataOps;
-import org.apache.doris.datasource.iceberg.IcebergMetadataOps;
-import org.apache.doris.fs.FileSystemProvider;
+import org.apache.doris.backup.Status;
+import org.apache.doris.catalog.TableIf;
+import org.apache.doris.fsv2.remote.RemoteFile;
 
-import java.util.concurrent.Executor;
+import java.util.ArrayList;
+import java.util.List;
 
-public class TransactionManagerFactory {
-
-    public static TransactionManager 
createHiveTransactionManager(HiveMetadataOps ops,
-            FileSystemProvider fileSystemProvider, Executor 
fileSystemExecutor) {
-        return new HiveTransactionManager(ops, fileSystemProvider, 
fileSystemExecutor);
-    }
-
-    public static TransactionManager 
createIcebergTransactionManager(IcebergMetadataOps ops) {
-        return new IcebergTransactionManager(ops);
+public class FileSystemDirectoryLister implements DirectoryLister {
+    public RemoteIterator<RemoteFile> listFiles(FileSystem fs, boolean 
recursive, TableIf table, String location)
+            throws FileSystemIOException {
+        List<RemoteFile> result = new ArrayList<>();
+        Status status = fs.listFiles(location, recursive, result);
+        if (!status.ok()) {
+            throw new FileSystemIOException(status.getErrCode(), 
status.getErrMsg());
+        }
+        return new RemoteFileRemoteIterator(result);
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemFactory.java 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemFactory.java
index 319188c9109..162d440f2d0 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemFactory.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemFactory.java
@@ -24,6 +24,7 @@ import 
org.apache.doris.datasource.property.storage.StorageProperties;
 import org.apache.doris.fsv2.remote.BrokerFileSystem;
 import org.apache.doris.fsv2.remote.RemoteFileSystem;
 
+import java.util.List;
 import java.util.Map;
 
 public class FileSystemFactory {
@@ -52,6 +53,22 @@ public class FileSystemFactory {
         return new BrokerFileSystem(name, properties);
     }
 
+    public static RemoteFileSystem get(FileSystemType fileSystemType, 
Map<String, String> properties,
+                                       String bindBrokerName)
+            throws UserException {
+        if (fileSystemType == FileSystemType.BROKER) {
+            return new BrokerFileSystem(bindBrokerName, properties);
+        }
+        List<StorageProperties> storagePropertiesList = 
StorageProperties.createAll(properties);
+
+        for (StorageProperties storageProperties : storagePropertiesList) {
+            if 
(storageProperties.getStorageName().equalsIgnoreCase(fileSystemType.name())) {
+                return StorageTypeMapper.create(storageProperties);
+            }
+        }
+        throw new RuntimeException("Unsupported file system type: " + 
fileSystemType);
+    }
+
     public static RemoteFileSystem get(BrokerDesc brokerDesc) {
         if (null != brokerDesc.getStorageProperties()) {
             return get(brokerDesc.getStorageProperties());
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemIOException.java 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemIOException.java
new file mode 100644
index 00000000000..5e1e569b76a
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemIOException.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 org.apache.doris.fsv2;
+
+import org.apache.doris.backup.Status.ErrCode;
+
+import java.io.IOException;
+import java.util.Optional;
+import javax.annotation.Nullable;
+
+public class FileSystemIOException extends IOException {
+
+    @Nullable
+    private ErrCode errCode;
+
+    public FileSystemIOException(ErrCode errCode, String message) {
+        super(message);
+        this.errCode = errCode;
+    }
+
+    public FileSystemIOException(ErrCode errCode, String message, Throwable 
cause) {
+        super(message, cause);
+        this.errCode = errCode;
+    }
+
+    public FileSystemIOException(String message) {
+        super(message);
+        this.errCode = null;
+    }
+
+    public FileSystemIOException(String message, Throwable cause) {
+        super(message, cause);
+        this.errCode = null;
+    }
+
+    public Optional<ErrCode> getErrorCode() {
+        return Optional.ofNullable(errCode);
+    }
+
+    @Override
+    public String getMessage() {
+        if (errCode != null) {
+            return String.format("[%s]: %s",
+                    errCode,
+                    super.getMessage());
+        } else {
+            return super.getMessage();
+        }
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
 b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemProvider.java
similarity index 53%
copy from 
fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
copy to fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemProvider.java
index b8898d9b279..4fc5ac316f0 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemProvider.java
@@ -15,22 +15,10 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package org.apache.doris.transaction;
+package org.apache.doris.fsv2;
 
-import org.apache.doris.datasource.hive.HiveMetadataOps;
-import org.apache.doris.datasource.iceberg.IcebergMetadataOps;
-import org.apache.doris.fs.FileSystemProvider;
+import org.apache.doris.datasource.SessionContext;
 
-import java.util.concurrent.Executor;
-
-public class TransactionManagerFactory {
-
-    public static TransactionManager 
createHiveTransactionManager(HiveMetadataOps ops,
-            FileSystemProvider fileSystemProvider, Executor 
fileSystemExecutor) {
-        return new HiveTransactionManager(ops, fileSystemProvider, 
fileSystemExecutor);
-    }
-
-    public static TransactionManager 
createIcebergTransactionManager(IcebergMetadataOps ops) {
-        return new IcebergTransactionManager(ops);
-    }
+public interface FileSystemProvider {
+    FileSystem get(SessionContext ctx);
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemProviderImpl.java 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemProviderImpl.java
new file mode 100644
index 00000000000..f664012c6ac
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemProviderImpl.java
@@ -0,0 +1,43 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.fsv2;
+
+import org.apache.doris.datasource.ExternalMetaCacheMgr;
+import org.apache.doris.datasource.SessionContext;
+import org.apache.doris.fsv2.remote.SwitchingFileSystem;
+
+import java.util.Map;
+
+public class FileSystemProviderImpl implements FileSystemProvider {
+    private ExternalMetaCacheMgr extMetaCacheMgr;
+    private String bindBrokerName;
+
+    private Map<String, String> properties;
+
+    public FileSystemProviderImpl(ExternalMetaCacheMgr extMetaCacheMgr, String 
bindBrokerName,
+                                  Map<String, String> properties) {
+        this.extMetaCacheMgr = extMetaCacheMgr;
+        this.bindBrokerName = bindBrokerName;
+        this.properties = properties;
+    }
+
+    @Override
+    public FileSystem get(SessionContext ctx) {
+        return new SwitchingFileSystem(extMetaCacheMgr, bindBrokerName, 
properties);
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemType.java 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemType.java
new file mode 100644
index 00000000000..f586da232c8
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemType.java
@@ -0,0 +1,49 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.fsv2;
+
+// TODO: [FileSystemType Unification]
+// There are currently multiple definitions of file system types across the 
codebase, including but not limited to:
+// 1. Backend module (e.g., FileSystemBackendType)
+// 2. Location/path parsing logic (e.g., LocationType or string-based tags)
+// 3. This enum: FileSystemType (used in the SPI/plugin layer)
+//
+// Problem:
+// - File system type definitions are scattered across different modules with 
inconsistent naming and granularity
+// - Adding a new type requires changes in multiple places, increasing risk of 
bugs and maintenance overhead
+// - Difficult to maintain and error-prone
+//
+// Refactoring Goal:
+// - Consolidate file system type definitions into a single source of truth
+// - Clearly define the semantics and usage of each type (e.g., remote vs 
local, object storage vs file system)
+// - All modules should reference the unified definition to avoid duplication 
and hardcoded strings
+//
+// Suggested Approach:
+// - Create a centralized `FsType` enum/class as the canonical definition
+// - Provide mapping or adapter methods where needed (e.g., map LocationType 
to FsType)
+// - Gradually deprecate other definitions and annotate them with @Deprecated, 
including migration instructions
+//
+public enum FileSystemType {
+    S3,
+    HDFS,
+    OFS,
+    JFS,
+    BROKER,
+    FILE,
+    AZURE
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemUtil.java 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemUtil.java
new file mode 100644
index 00000000000..a3cbf6369fb
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/FileSystemUtil.java
@@ -0,0 +1,70 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.fsv2;
+
+import org.apache.doris.backup.Status;
+
+import org.apache.hadoop.fs.Path;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class FileSystemUtil {
+
+    public static void asyncRenameFiles(FileSystem fs,
+                                        Executor executor,
+                                        List<CompletableFuture<?>> 
renameFileFutures,
+                                        AtomicBoolean cancelled,
+                                        String origFilePath,
+                                        String destFilePath,
+                                        List<String> fileNames) {
+        for (String fileName : fileNames) {
+            Path source = new Path(origFilePath, fileName);
+            Path target = new Path(destFilePath, fileName);
+            renameFileFutures.add(CompletableFuture.runAsync(() -> {
+                if (cancelled.get()) {
+                    return;
+                }
+                Status status = fs.rename(source.toString(), 
target.toString());
+                if (!status.ok()) {
+                    throw new RuntimeException(status.getErrMsg());
+                }
+            }, executor));
+        }
+    }
+
+    public static void asyncRenameDir(FileSystem fs,
+                                      Executor executor,
+                                      List<CompletableFuture<?>> 
renameFileFutures,
+                                      AtomicBoolean cancelled,
+                                      String origFilePath,
+                                      String destFilePath,
+                                      Runnable runWhenPathNotExist) {
+        renameFileFutures.add(CompletableFuture.runAsync(() -> {
+            if (cancelled.get()) {
+                return;
+            }
+            Status status = fs.renameDir(origFilePath, destFilePath, 
runWhenPathNotExist);
+            if (!status.ok()) {
+                throw new RuntimeException(status.getErrMsg());
+            }
+        }, executor));
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/fsv2/LocalDfsFileSystem.java 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/LocalDfsFileSystem.java
new file mode 100644
index 00000000000..4b2a12e8597
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/LocalDfsFileSystem.java
@@ -0,0 +1,199 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.fsv2;
+
+import org.apache.doris.backup.Status;
+import org.apache.doris.fsv2.remote.RemoteFile;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.LocalFileSystem;
+import org.apache.hadoop.fs.LocatedFileStatus;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.RemoteIterator;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class LocalDfsFileSystem implements FileSystem {
+
+    public LocalFileSystem fs = LocalFileSystem.getLocal(new Configuration());
+
+    public LocalDfsFileSystem() throws IOException {
+    }
+
+    @Override
+    public Map<String, String> getProperties() {
+        return null;
+    }
+
+    @Override
+    public Status directoryExists(String dir) {
+        return exists(dir);
+    }
+
+    @Override
+    public Status exists(String remotePath) {
+        boolean exists = false;
+        try {
+            exists = fs.exists(new Path(remotePath));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        if (exists) {
+            return Status.OK;
+        } else {
+            return new Status(Status.ErrCode.NOT_FOUND, "");
+        }
+    }
+
+    @Override
+    public Status downloadWithFileSize(String remoteFilePath, String 
localFilePath, long fileSize) {
+        return null;
+    }
+
+    @Override
+    public Status upload(String localPath, String remotePath) {
+        return null;
+    }
+
+    @Override
+    public Status directUpload(String content, String remoteFile) {
+        return null;
+    }
+
+    @Override
+    public Status rename(String origFilePath, String destFilePath) {
+        try {
+            fs.rename(new Path(origFilePath), new Path(destFilePath));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return Status.OK;
+    }
+
+    @Override
+    public Status renameDir(String origFilePath, String destFilePath, Runnable 
runWhenPathNotExist) {
+        Status status = exists(destFilePath);
+        if (status.ok()) {
+            throw new RuntimeException("Destination directory already exists: 
" + destFilePath);
+        }
+        String targetParent = new Path(destFilePath).getParent().toString();
+        status = exists(targetParent);
+        if (Status.ErrCode.NOT_FOUND.equals(status.getErrCode())) {
+            status = makeDir(targetParent);
+        }
+        if (!status.ok()) {
+            throw new RuntimeException(status.getErrMsg());
+        }
+
+        runWhenPathNotExist.run();
+
+        return rename(origFilePath, destFilePath);
+    }
+
+    @Override
+    public Status delete(String remotePath) {
+        try {
+            fs.delete(new Path(remotePath), true);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return Status.OK;
+    }
+
+    @Override
+    public Status makeDir(String remotePath) {
+        try {
+            fs.mkdirs(new Path(remotePath));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return Status.OK;
+    }
+
+    @Override
+    public Status globList(String remotePath, List<RemoteFile> result, boolean 
fileNameOnly) {
+        try {
+            FileStatus[] locatedFileStatusRemoteIterator = fs.globStatus(new 
Path(remotePath));
+            if (locatedFileStatusRemoteIterator == null) {
+                return Status.OK;
+            }
+            for (FileStatus fileStatus : locatedFileStatusRemoteIterator) {
+                RemoteFile remoteFile = new RemoteFile(
+                        fileNameOnly ? fileStatus.getPath().getName() : 
fileStatus.getPath().toString(),
+                        !fileStatus.isDirectory(), fileStatus.isDirectory() ? 
-1 : fileStatus.getLen(),
+                        fileStatus.getBlockSize(), 
fileStatus.getModificationTime());
+                result.add(remoteFile);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return Status.OK;
+    }
+
+    @Override
+    public Status listFiles(String remotePath, boolean recursive, 
List<RemoteFile> result) {
+        try {
+            Path locatedPath = new Path(remotePath);
+            RemoteIterator<LocatedFileStatus> locatedFiles = 
fs.listFiles(locatedPath, recursive);
+            while (locatedFiles.hasNext()) {
+                LocatedFileStatus fileStatus = locatedFiles.next();
+                RemoteFile location = new RemoteFile(
+                        fileStatus.getPath(), fileStatus.isDirectory(), 
fileStatus.getLen(),
+                        fileStatus.getBlockSize(), 
fileStatus.getModificationTime(), fileStatus.getBlockLocations());
+                result.add(location);
+            }
+        } catch (FileNotFoundException e) {
+            return new Status(Status.ErrCode.NOT_FOUND, e.getMessage());
+        } catch (Exception e) {
+            return new Status(Status.ErrCode.COMMON_ERROR, e.getMessage());
+        }
+        return Status.OK;
+    }
+
+    @Override
+    public Status listDirectories(String remotePath, Set<String> result) {
+        try {
+            FileStatus[] fileStatuses = fs.listStatus(new Path(remotePath));
+            result.addAll(
+                    Arrays.stream(fileStatuses)
+                            .filter(FileStatus::isDirectory)
+                            .map(file -> file.getPath().toString() + "/")
+                            .collect(ImmutableSet.toImmutableSet()));
+        } catch (IOException e) {
+            return new Status(Status.ErrCode.COMMON_ERROR, e.getMessage());
+        }
+        return Status.OK;
+    }
+
+    public void createFile(String path) throws IOException {
+        Path path1 = new Path(path);
+        if (!exists(path1.getParent().toString()).ok()) {
+            makeDir(path1.getParent().toString());
+        }
+        FSDataOutputStream build = fs.createFile(path1).build();
+        build.close();
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/fsv2/RemoteFileRemoteIterator.java 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/RemoteFileRemoteIterator.java
new file mode 100644
index 00000000000..1ee41aaecd0
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/RemoteFileRemoteIterator.java
@@ -0,0 +1,47 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.fsv2;
+
+import org.apache.doris.fsv2.remote.RemoteFile;
+
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+public class RemoteFileRemoteIterator
+        implements RemoteIterator<RemoteFile> {
+    private final List<RemoteFile> remoteFileList;
+    private int currentIndex = 0;
+
+    public RemoteFileRemoteIterator(List<RemoteFile> remoteFileList) {
+        this.remoteFileList = Objects.requireNonNull(remoteFileList, "iterator 
is null");
+    }
+
+    @Override
+    public boolean hasNext() throws FileSystemIOException {
+        return currentIndex < remoteFileList.size();
+    }
+
+    @Override
+    public RemoteFile next() throws FileSystemIOException {
+        if (!hasNext()) {
+            throw new NoSuchElementException("No more elements in 
RemoteFileRemoteIterator");
+        }
+        return remoteFileList.get(currentIndex++);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
 b/fe/fe-core/src/main/java/org/apache/doris/fsv2/RemoteFiles.java
similarity index 54%
copy from 
fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
copy to fe/fe-core/src/main/java/org/apache/doris/fsv2/RemoteFiles.java
index b8898d9b279..54a80af5891 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/RemoteFiles.java
@@ -15,22 +15,21 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package org.apache.doris.transaction;
+package org.apache.doris.fsv2;
 
-import org.apache.doris.datasource.hive.HiveMetadataOps;
-import org.apache.doris.datasource.iceberg.IcebergMetadataOps;
-import org.apache.doris.fs.FileSystemProvider;
+import org.apache.doris.fs.remote.RemoteFile;
 
-import java.util.concurrent.Executor;
+import java.util.List;
 
-public class TransactionManagerFactory {
+public class RemoteFiles {
 
-    public static TransactionManager 
createHiveTransactionManager(HiveMetadataOps ops,
-            FileSystemProvider fileSystemProvider, Executor 
fileSystemExecutor) {
-        return new HiveTransactionManager(ops, fileSystemProvider, 
fileSystemExecutor);
+    private final List<RemoteFile> files;
+
+    public RemoteFiles(List<RemoteFile> files) {
+        this.files = files;
     }
 
-    public static TransactionManager 
createIcebergTransactionManager(IcebergMetadataOps ops) {
-        return new IcebergTransactionManager(ops);
+    public List<RemoteFile> files() {
+        return files;
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
 b/fe/fe-core/src/main/java/org/apache/doris/fsv2/RemoteIterator.java
similarity index 53%
copy from 
fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
copy to fe/fe-core/src/main/java/org/apache/doris/fsv2/RemoteIterator.java
index b8898d9b279..9f93e3eb549 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/RemoteIterator.java
@@ -14,23 +14,14 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+// This file is copied from
+// 
https://github.com/trinodb/trino/blob/438/plugin/trino-hive/src/main/java/io/trino/plugin/hive/fs/RemoteIterator.java
+// and modified by Doris
 
-package org.apache.doris.transaction;
+package org.apache.doris.fsv2;
 
-import org.apache.doris.datasource.hive.HiveMetadataOps;
-import org.apache.doris.datasource.iceberg.IcebergMetadataOps;
-import org.apache.doris.fs.FileSystemProvider;
+public interface RemoteIterator<T> {
+    boolean hasNext() throws FileSystemIOException;
 
-import java.util.concurrent.Executor;
-
-public class TransactionManagerFactory {
-
-    public static TransactionManager 
createHiveTransactionManager(HiveMetadataOps ops,
-            FileSystemProvider fileSystemProvider, Executor 
fileSystemExecutor) {
-        return new HiveTransactionManager(ops, fileSystemProvider, 
fileSystemExecutor);
-    }
-
-    public static TransactionManager 
createIcebergTransactionManager(IcebergMetadataOps ops) {
-        return new IcebergTransactionManager(ops);
-    }
+    T next() throws FileSystemIOException;
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
 b/fe/fe-core/src/main/java/org/apache/doris/fsv2/SimpleRemoteIterator.java
similarity index 50%
copy from 
fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
copy to fe/fe-core/src/main/java/org/apache/doris/fsv2/SimpleRemoteIterator.java
index b8898d9b279..a631241fbcd 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/fsv2/SimpleRemoteIterator.java
@@ -15,22 +15,31 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package org.apache.doris.transaction;
 
-import org.apache.doris.datasource.hive.HiveMetadataOps;
-import org.apache.doris.datasource.iceberg.IcebergMetadataOps;
-import org.apache.doris.fs.FileSystemProvider;
+package org.apache.doris.fsv2;
 
-import java.util.concurrent.Executor;
+import org.apache.doris.fsv2.remote.RemoteFile;
 
-public class TransactionManagerFactory {
+import java.util.Iterator;
+import java.util.Objects;
+// This file is copied from
+// 
https://github.com/trinodb/trino/blob/438/plugin/trino-hive/src/main/java/io/trino/plugin/hive/fs/SimpleRemoteIterator.java
+// and modified by Doris
 
-    public static TransactionManager 
createHiveTransactionManager(HiveMetadataOps ops,
-            FileSystemProvider fileSystemProvider, Executor 
fileSystemExecutor) {
-        return new HiveTransactionManager(ops, fileSystemProvider, 
fileSystemExecutor);
+class SimpleRemoteIterator implements RemoteIterator<RemoteFile> {
+    private final Iterator<RemoteFile> iterator;
+
+    public SimpleRemoteIterator(Iterator<RemoteFile> iterator) {
+        this.iterator = Objects.requireNonNull(iterator, "iterator is null");
+    }
+
+    @Override
+    public boolean hasNext() throws FileSystemIOException {
+        return iterator.hasNext();
     }
 
-    public static TransactionManager 
createIcebergTransactionManager(IcebergMetadataOps ops) {
-        return new IcebergTransactionManager(ops);
+    @Override
+    public RemoteFile next() throws FileSystemIOException {
+        return iterator.next();
     }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/fsv2/TransactionDirectoryListingCacheKey.java
 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/TransactionDirectoryListingCacheKey.java
new file mode 100644
index 00000000000..ba4f80c3762
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/TransactionDirectoryListingCacheKey.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.
+// This file is copied from
+// 
https://github.com/trinodb/trino/blob/438/plugin/trino-hive/src/main/java/io/trino/plugin/hive/fs/TransactionDirectoryListingCacheKey.java
+// and modified by Doris
+
+package org.apache.doris.fsv2;
+
+import java.util.Objects;
+
+public class TransactionDirectoryListingCacheKey {
+
+    private final long transactionId;
+    private final String path;
+
+    public TransactionDirectoryListingCacheKey(long transactionId, String 
path) {
+        this.transactionId = transactionId;
+        this.path = Objects.requireNonNull(path, "path is null");
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TransactionDirectoryListingCacheKey that = 
(TransactionDirectoryListingCacheKey) o;
+        return transactionId == that.transactionId && path.equals(that.path);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(transactionId, path);
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new 
StringBuilder("TransactionDirectoryListingCacheKey{");
+        sb.append("transactionId=").append(transactionId);
+        sb.append(", path='").append(path).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/fsv2/TransactionScopeCachingDirectoryLister.java
 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/TransactionScopeCachingDirectoryLister.java
new file mode 100644
index 00000000000..f7bdc5e3c05
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/TransactionScopeCachingDirectoryLister.java
@@ -0,0 +1,219 @@
+// 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.
+// This file is copied from
+// 
https://github.com/trinodb/trino/blob/438/plugin/trino-hive/src/main/java/io/trino/plugin/hive/fs/TransactionScopeCachingDirectoryLister.java
+// and modified by Doris
+
+package org.apache.doris.fsv2;
+
+import org.apache.doris.catalog.TableIf;
+import org.apache.doris.fsv2.remote.RemoteFile;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import com.google.common.cache.Cache;
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import com.google.errorprone.annotations.concurrent.GuardedBy;
+import org.apache.commons.collections.ListUtils;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
+
+/**
+ * Caches directory content (including listings that were started 
concurrently).
+ * {@link TransactionScopeCachingDirectoryLister} assumes that all listings
+ * are performed by same user within single transaction, therefore any failure 
can
+ * be shared between concurrent listings.
+ */
+public class TransactionScopeCachingDirectoryLister implements DirectoryLister 
{
+    private final long transactionId;
+
+    @VisibleForTesting
+    public Cache<TransactionDirectoryListingCacheKey, FetchingValueHolder> 
getCache() {
+        return cache;
+    }
+
+    //TODO use a cache key based on Path & SchemaTableName and iterate over 
the cache keys
+    // to deal more efficiently with cache invalidation scenarios for 
partitioned tables.
+    private final Cache<TransactionDirectoryListingCacheKey, 
FetchingValueHolder> cache;
+    private final DirectoryLister delegate;
+
+    public TransactionScopeCachingDirectoryLister(DirectoryLister delegate, 
long transactionId,
+                                                  
Cache<TransactionDirectoryListingCacheKey,
+                                                          FetchingValueHolder> 
cache) {
+        this.delegate = Objects.requireNonNull(delegate, "delegate is null");
+        this.transactionId = transactionId;
+        this.cache = Objects.requireNonNull(cache, "cache is null");
+    }
+
+    @Override
+    public RemoteIterator<RemoteFile> listFiles(FileSystem fs, boolean 
recursive, TableIf table, String location)
+            throws FileSystemIOException {
+        return listInternal(fs, recursive, table, new 
TransactionDirectoryListingCacheKey(transactionId, location));
+    }
+
+    private RemoteIterator<RemoteFile> listInternal(FileSystem fs, boolean 
recursive, TableIf table,
+                                                    
TransactionDirectoryListingCacheKey cacheKey)
+            throws FileSystemIOException {
+        FetchingValueHolder cachedValueHolder;
+        try {
+            cachedValueHolder = cache.get(cacheKey,
+                    () -> new 
FetchingValueHolder(createListingRemoteIterator(fs, recursive, table, 
cacheKey)));
+        } catch (ExecutionException | UncheckedExecutionException e) {
+            Throwable throwable = e.getCause();
+            Throwables.throwIfInstanceOf(throwable, 
FileSystemIOException.class);
+            Throwables.throwIfUnchecked(throwable);
+            throw new RuntimeException("Failed to list directory: " + 
cacheKey.getPath(), throwable);
+        }
+
+        if (cachedValueHolder.isFullyCached()) {
+            return new 
SimpleRemoteIterator(cachedValueHolder.getCachedFiles());
+        }
+
+        return cachingRemoteIterator(cachedValueHolder, cacheKey);
+    }
+
+    private RemoteIterator<RemoteFile> createListingRemoteIterator(FileSystem 
fs, boolean recursive,
+                                                                   TableIf 
table,
+                                                                   
TransactionDirectoryListingCacheKey cacheKey)
+            throws FileSystemIOException {
+        return delegate.listFiles(fs, recursive, table, cacheKey.getPath());
+    }
+
+
+    private RemoteIterator<RemoteFile> 
cachingRemoteIterator(FetchingValueHolder cachedValueHolder,
+                                                             
TransactionDirectoryListingCacheKey cacheKey) {
+        return new RemoteIterator<RemoteFile>() {
+            private int fileIndex;
+
+            @Override
+            public boolean hasNext()
+                    throws FileSystemIOException {
+                try {
+                    boolean hasNext = 
cachedValueHolder.getCachedFile(fileIndex).isPresent();
+                    // Update cache weight of cachedValueHolder for a given 
path.
+                    // The cachedValueHolder acts as an invalidation guard.
+                    // If a cache invalidation happens while this iterator 
goes over the files from the specified path,
+                    // the eventually outdated file listing will not be added 
anymore to the cache.
+                    cache.asMap().replace(cacheKey, cachedValueHolder, 
cachedValueHolder);
+                    return hasNext;
+                } catch (Exception exception) {
+                    // invalidate cached value to force retry of directory 
listing
+                    cache.invalidate(cacheKey);
+                    throw exception;
+                }
+            }
+
+            @Override
+            public RemoteFile next()
+                    throws FileSystemIOException {
+                // force cache entry weight update in case next file is cached
+                Preconditions.checkState(hasNext());
+                return 
cachedValueHolder.getCachedFile(fileIndex++).orElseThrow(NoSuchElementException::new);
+            }
+        };
+    }
+
+    @VisibleForTesting
+    boolean isCached(String location) {
+        return isCached(new TransactionDirectoryListingCacheKey(transactionId, 
location));
+    }
+
+    @VisibleForTesting
+    boolean isCached(TransactionDirectoryListingCacheKey cacheKey) {
+        FetchingValueHolder cached = cache.getIfPresent(cacheKey);
+        return cached != null && cached.isFullyCached();
+    }
+
+    static class FetchingValueHolder {
+
+        private final List<RemoteFile> cachedFiles = 
ListUtils.synchronizedList(new ArrayList<RemoteFile>());
+
+        @GuardedBy("this")
+        @Nullable
+        private RemoteIterator<RemoteFile> fileIterator;
+        @GuardedBy("this")
+        @Nullable
+        private Exception exception;
+
+        public FetchingValueHolder(RemoteIterator<RemoteFile> fileIterator) {
+            this.fileIterator = Objects.requireNonNull(fileIterator, 
"fileIterator is null");
+        }
+
+        public synchronized boolean isFullyCached() {
+            return fileIterator == null && exception == null;
+        }
+
+        public long getCacheFileCount() {
+            return cachedFiles.size();
+        }
+
+        public Iterator<RemoteFile> getCachedFiles() {
+            Preconditions.checkState(isFullyCached());
+            return cachedFiles.iterator();
+        }
+
+        public Optional<RemoteFile> getCachedFile(int index)
+                throws FileSystemIOException {
+            int filesSize = cachedFiles.size();
+            Preconditions.checkArgument(index >= 0 && index <= filesSize,
+                    "File index (%s) out of bounds [0, %s]", index, filesSize);
+
+            // avoid fileIterator synchronization (and thus blocking) for 
already cached files
+            if (index < filesSize) {
+                return Optional.of(cachedFiles.get(index));
+            }
+
+            return fetchNextCachedFile(index);
+        }
+
+        private synchronized Optional<RemoteFile> fetchNextCachedFile(int 
index)
+                throws FileSystemIOException {
+            if (exception != null) {
+                throw new FileSystemIOException("Exception while listing 
directory", exception);
+            }
+
+            if (index < cachedFiles.size()) {
+                // file was fetched concurrently
+                return Optional.of(cachedFiles.get(index));
+            }
+
+            try {
+                if (fileIterator == null || !fileIterator.hasNext()) {
+                    // no more files
+                    fileIterator = null;
+                    return Optional.empty();
+                }
+
+                RemoteFile fileStatus = fileIterator.next();
+                cachedFiles.add(fileStatus);
+                return Optional.of(fileStatus);
+            } catch (Exception exception) {
+                fileIterator = null;
+                this.exception = exception;
+                throw exception;
+            }
+        }
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/fs/remote/SwitchingFileSystem.java 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/remote/SwitchingFileSystem.java
similarity index 95%
copy from 
fe/fe-core/src/main/java/org/apache/doris/fs/remote/SwitchingFileSystem.java
copy to 
fe/fe-core/src/main/java/org/apache/doris/fsv2/remote/SwitchingFileSystem.java
index ab7c91d693a..7ef6b462831 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/fs/remote/SwitchingFileSystem.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/fsv2/remote/SwitchingFileSystem.java
@@ -15,13 +15,13 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package org.apache.doris.fs.remote;
+package org.apache.doris.fsv2.remote;
 
 import org.apache.doris.backup.Status;
 import org.apache.doris.common.util.LocationPath;
 import org.apache.doris.datasource.ExternalMetaCacheMgr;
-import org.apache.doris.fs.FileSystem;
-import org.apache.doris.fs.FileSystemCache;
+import org.apache.doris.fsv2.FileSystem;
+import org.apache.doris.fsv2.FileSystemCache;
 
 import java.util.List;
 import java.util.Map;
@@ -36,7 +36,7 @@ public class SwitchingFileSystem implements FileSystem {
     private final Map<String, String> properties;
 
     public SwitchingFileSystem(ExternalMetaCacheMgr extMetaCacheMgr, String 
bindBrokerName,
-            Map<String, String> properties) {
+                               Map<String, String> properties) {
         this.extMetaCacheMgr = extMetaCacheMgr;
         this.bindBrokerName = bindBrokerName;
         this.properties = properties;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/HiveTransactionManager.java
 
b/fe/fe-core/src/main/java/org/apache/doris/transaction/HiveTransactionManager.java
index 65f0c2bd5e3..5839643d770 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/HiveTransactionManager.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/transaction/HiveTransactionManager.java
@@ -19,7 +19,7 @@ package org.apache.doris.transaction;
 
 import org.apache.doris.datasource.hive.HMSTransaction;
 import org.apache.doris.datasource.hive.HiveMetadataOps;
-import org.apache.doris.fs.FileSystemProvider;
+import org.apache.doris.fsv2.FileSystemProvider;
 
 import java.util.concurrent.Executor;
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
 
b/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
index b8898d9b279..fe6699626a7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionManagerFactory.java
@@ -19,7 +19,7 @@ package org.apache.doris.transaction;
 
 import org.apache.doris.datasource.hive.HiveMetadataOps;
 import org.apache.doris.datasource.iceberg.IcebergMetadataOps;
-import org.apache.doris.fs.FileSystemProvider;
+import org.apache.doris.fsv2.FileSystemProvider;
 
 import java.util.concurrent.Executor;
 
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/common/util/LocationPathTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/common/util/LocationPathTest.java
index e49302cef7f..034b5479758 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/common/util/LocationPathTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/common/util/LocationPathTest.java
@@ -20,7 +20,7 @@ package org.apache.doris.common.util;
 import org.apache.doris.catalog.HdfsResource;
 import org.apache.doris.common.util.LocationPath.Scheme;
 import org.apache.doris.datasource.property.constants.OssProperties;
-import org.apache.doris.fs.FileSystemType;
+import org.apache.doris.fsv2.FileSystemType;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -41,7 +41,7 @@ public class LocationPathTest {
         String beLocation = locationPath.toStorageLocation().toString();
         Assertions.assertTrue(beLocation.startsWith("hdfs://"));
         Assertions.assertEquals(LocationPath.getFSIdentity(beLocation, 
Collections.emptyMap(), null).first,
-                FileSystemType.DFS);
+                FileSystemType.HDFS);
 
         // HA props
         Map<String, String> props = new HashMap<>();
@@ -130,13 +130,13 @@ public class LocationPathTest {
         locationPath = new 
LocationPath("oss://test.oss-dls.aliyuncs.com/path", rangeProps);
         Assertions.assertEquals("oss://test.oss-dls.aliyuncs.com/path", 
locationPath.get());
         Assertions.assertEquals(LocationPath.getFSIdentity(locationPath.get(), 
rangeProps, null).first,
-                FileSystemType.DFS);
+                FileSystemType.HDFS);
         // FE
         
Assertions.assertTrue(locationPath.get().startsWith("oss://test.oss-dls.aliyuncs"));
         // BE
         beLocation = locationPath.toStorageLocation().toString();
         
Assertions.assertTrue(beLocation.startsWith("oss://test.oss-dls.aliyuncs"));
-        Assertions.assertEquals(locationPath.getFileSystemType(), 
FileSystemType.DFS);
+        Assertions.assertEquals(locationPath.getFileSystemType(), 
FileSystemType.HDFS);
     }
 
     @Test
@@ -177,7 +177,7 @@ public class LocationPathTest {
         beLocation = locationPath.toStorageLocation().toString();
         Assertions.assertTrue(beLocation.startsWith("gfs://"));
         Assertions.assertEquals(LocationPath.getFSIdentity(beLocation, 
Collections.emptyMap(), null).first,
-                FileSystemType.DFS);
+                FileSystemType.HDFS);
     }
 
     @Test
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/hive/HmsCommitTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/hive/HmsCommitTest.java
index d859338a1d2..ce20da09e14 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/hive/HmsCommitTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/hive/HmsCommitTest.java
@@ -24,10 +24,10 @@ import org.apache.doris.catalog.PrimitiveType;
 import org.apache.doris.common.util.DebugUtil;
 import org.apache.doris.datasource.NameMapping;
 import org.apache.doris.datasource.TestHMSCachedClient;
-import org.apache.doris.fs.FileSystem;
-import org.apache.doris.fs.FileSystemProvider;
-import org.apache.doris.fs.LocalDfsFileSystem;
-import org.apache.doris.fs.remote.SwitchingFileSystem;
+import org.apache.doris.fsv2.FileSystem;
+import org.apache.doris.fsv2.FileSystemProvider;
+import org.apache.doris.fsv2.LocalDfsFileSystem;
+import org.apache.doris.fsv2.remote.SwitchingFileSystem;
 import 
org.apache.doris.nereids.trees.plans.commands.insert.HiveInsertCommandContext;
 import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.thrift.THiveLocationParams;
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/fs/remote/RemoteFileSystemTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/fs/remote/RemoteFileSystemTest.java
deleted file mode 100644
index 3fc15ab8e37..00000000000
--- 
a/fe/fe-core/src/test/java/org/apache/doris/fs/remote/RemoteFileSystemTest.java
+++ /dev/null
@@ -1,158 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package org.apache.doris.fs.remote;
-
-import org.apache.doris.common.Pair;
-import org.apache.doris.common.UserException;
-import org.apache.doris.common.security.authentication.AuthenticationConfig;
-import org.apache.doris.common.security.authentication.HadoopAuthenticator;
-import 
org.apache.doris.common.security.authentication.HadoopKerberosAuthenticator;
-import 
org.apache.doris.common.security.authentication.HadoopSimpleAuthenticator;
-import org.apache.doris.common.util.LocationPath;
-import org.apache.doris.fs.FileSystemCache;
-import org.apache.doris.fs.FileSystemType;
-import org.apache.doris.fs.remote.dfs.DFSFileSystem;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableMap;
-import mockit.Mock;
-import mockit.MockUp;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
-import org.apache.hadoop.fs.LocalFileSystem;
-import org.apache.hadoop.security.UserGroupInformation;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.io.IOException;
-import java.net.URI;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Map;
-
-public class RemoteFileSystemTest {
-
-    @Test
-    public void testFilesystemAndAuthType() throws UserException {
-
-        // These paths should use s3 filesystem, and use simple auth
-        ArrayList<String> s3Paths = new ArrayList<>();
-        s3Paths.add("s3://a/b/c");
-        s3Paths.add("s3a://a/b/c");
-        s3Paths.add("s3n://a/b/c");
-        s3Paths.add("oss://a/b/c");  // default use s3 filesystem
-        s3Paths.add("gs://a/b/c");
-        s3Paths.add("bos://a/b/c");
-        s3Paths.add("cos://a/b/c");
-        s3Paths.add("cosn://a/b/c");
-        s3Paths.add("lakefs://a/b/c");
-        s3Paths.add("obs://a/b/c");
-
-        // These paths should use dfs filesystem, and auth will be changed by 
configure
-        ArrayList<String> dfsPaths = new ArrayList<>();
-        dfsPaths.add("ofs://a/b/c");
-        dfsPaths.add("gfs://a/b/c");
-        dfsPaths.add("hdfs://a/b/c");
-        dfsPaths.add("oss://a/b/c");  // if endpoint contains 
'oss-dls.aliyuncs', will use dfs filesystem
-
-        new MockUp<UserGroupInformation>(UserGroupInformation.class) {
-            @Mock
-            public <T> T doAs(PrivilegedExceptionAction<T> action) throws 
IOException, InterruptedException {
-                return (T) new LocalFileSystem();
-            }
-        };
-
-        new 
MockUp<HadoopKerberosAuthenticator>(HadoopKerberosAuthenticator.class) {
-            @Mock
-            public synchronized UserGroupInformation getUGI() throws 
IOException {
-                return UserGroupInformation.getCurrentUser();
-            }
-        };
-
-        Configuration confWithoutKerberos = new Configuration();
-
-        Configuration confWithKerberosIncomplete = new Configuration();
-        
confWithKerberosIncomplete.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION,
 "kerberos");
-
-        Configuration confWithKerberos = new Configuration();
-        
confWithKerberos.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION,
 "kerberos");
-        confWithKerberos.set(AuthenticationConfig.HADOOP_KERBEROS_PRINCIPAL, 
"principal");
-        confWithKerberos.set(AuthenticationConfig.HADOOP_KERBEROS_KEYTAB, 
"keytab");
-
-        ImmutableMap<String, String> s3props = ImmutableMap.of("s3.endpoint", 
"http://127.0.0.1";);
-        s3props.forEach(confWithKerberos::set);
-        s3props.forEach(confWithoutKerberos::set);
-        s3props.forEach(confWithKerberosIncomplete::set);
-
-        for (String path : s3Paths) {
-            checkS3Filesystem(path, confWithKerberos, s3props);
-        }
-        for (String path : s3Paths) {
-            checkS3Filesystem(path, confWithKerberosIncomplete, s3props);
-        }
-        for (String path : s3Paths) {
-            checkS3Filesystem(path, confWithoutKerberos, s3props);
-        }
-
-        s3props = ImmutableMap.of("s3.endpoint", 
"oss://xx-oss-dls.aliyuncs/abc");
-        System.setProperty("java.security.krb5.realm", "realm");
-        System.setProperty("java.security.krb5.kdc", "kdc");
-
-        for (String path : dfsPaths) {
-            checkDFSFilesystem(path, confWithKerberos, 
HadoopKerberosAuthenticator.class.getName(), s3props);
-        }
-        for (String path : dfsPaths) {
-            checkDFSFilesystem(path, confWithKerberosIncomplete, 
HadoopSimpleAuthenticator.class.getName(), s3props);
-        }
-        for (String path : dfsPaths) {
-            checkDFSFilesystem(path, confWithoutKerberos, 
HadoopSimpleAuthenticator.class.getName(), s3props);
-        }
-
-    }
-
-    private void checkS3Filesystem(String path, Configuration conf, 
Map<String, String> m) throws UserException {
-        RemoteFileSystem fs = createFs(path, conf, m);
-        Assert.assertTrue(fs instanceof S3FileSystem);
-        HadoopAuthenticator authenticator = ((S3FileSystem) 
fs).getAuthenticator();
-        Assert.assertTrue(authenticator instanceof HadoopSimpleAuthenticator);
-    }
-
-    private void checkDFSFilesystem(String path, Configuration conf, String 
authClass, Map<String, String> m) throws UserException {
-        RemoteFileSystem fs = createFs(path, conf, m);
-        Assert.assertTrue(fs instanceof DFSFileSystem);
-        HadoopAuthenticator authenticator = ((DFSFileSystem) 
fs).getAuthenticator();
-        Assert.assertEquals(authClass, authenticator.getClass().getName());
-    }
-
-    private RemoteFileSystem createFs(String path, Configuration conf, 
Map<String, String> m) throws UserException {
-        LocationPath locationPath = new LocationPath(path, m);
-        FileSystemType fileSystemType = locationPath.getFileSystemType();
-        URI uri = locationPath.getPath().toUri();
-        String fsIdent = Strings.nullToEmpty(uri.getScheme()) + "://" + 
Strings.nullToEmpty(uri.getAuthority());
-        FileSystemCache fileSystemCache = new FileSystemCache();
-        RemoteFileSystem fs = fileSystemCache.getRemoteFileSystem(
-            new FileSystemCache.FileSystemCacheKey(
-                Pair.of(fileSystemType, fsIdent),
-                ImmutableMap.of(),
-                null,
-                conf));
-        fs.nativeFileSystem(path);
-        return fs;
-    }
-
-}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/fsv2/remote/RemoteFileSystemTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/fsv2/remote/RemoteFileSystemTest.java
new file mode 100644
index 00000000000..df5225ff3c0
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/fsv2/remote/RemoteFileSystemTest.java
@@ -0,0 +1,172 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.fsv2.remote;
+
+import org.apache.doris.analysis.StorageBackend;
+import org.apache.doris.backup.Status;
+
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.LocatedFileStatus;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.RemoteIterator;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class RemoteFileSystemTest {
+
+    private RemoteFileSystem remoteFileSystem;
+    private FileSystem mockFileSystem;
+
+    @BeforeEach
+    void setUp() {
+        remoteFileSystem = Mockito.spy(new RemoteFileSystem("test", 
StorageBackend.StorageType.HDFS) {
+            @Override
+            public Status exists(String remotePath) {
+                return null;
+            }
+
+            @Override
+            public Status downloadWithFileSize(String remoteFilePath, String 
localFilePath, long fileSize) {
+                return null;
+            }
+
+            @Override
+            public Status upload(String localPath, String remotePath) {
+                return null;
+            }
+
+            @Override
+            public Status directUpload(String content, String remoteFile) {
+                return null;
+            }
+
+            @Override
+            public Status rename(String origFilePath, String destFilePath) {
+                return null;
+            }
+
+            @Override
+            public Status delete(String remotePath) {
+                return null;
+            }
+
+            @Override
+            public Status makeDir(String remotePath) {
+                return null;
+            }
+
+            @Override
+            public Status globList(String remotePath, List<RemoteFile> result, 
boolean fileNameOnly) {
+                return null;
+            }
+        });
+        mockFileSystem = Mockito.mock(FileSystem.class);
+    }
+
+    @Test
+    @DisplayName("listFiles should return OK status and populate result list 
when files are found")
+    void listFilesReturnsOkWhenFilesFound() throws Exception {
+        String remotePath = "/test/path";
+        List<RemoteFile> result = new ArrayList<>();
+        RemoteIterator<LocatedFileStatus> mockIterator = 
Mockito.mock(RemoteIterator.class);
+        LocatedFileStatus mockFileStatus = 
Mockito.mock(LocatedFileStatus.class);
+
+        
Mockito.doReturn(mockFileSystem).when(remoteFileSystem).nativeFileSystem(remotePath);
+        Mockito.when(mockFileSystem.listFiles(new Path(remotePath), 
true)).thenReturn(mockIterator);
+        Mockito.when(mockIterator.hasNext()).thenReturn(true, false);
+        Mockito.when(mockIterator.next()).thenReturn(mockFileStatus);
+        Mockito.when(mockFileStatus.getPath()).thenReturn(new 
Path("/test/path/file1"));
+        Mockito.when(mockFileStatus.isDirectory()).thenReturn(false);
+        Mockito.when(mockFileStatus.getLen()).thenReturn(100L);
+        Mockito.when(mockFileStatus.getBlockSize()).thenReturn(128L);
+        
Mockito.when(mockFileStatus.getModificationTime()).thenReturn(123456789L);
+        Mockito.when(mockFileStatus.getBlockLocations()).thenReturn(null);
+
+        Status status = remoteFileSystem.listFiles(remotePath, true, result);
+
+        Assertions.assertEquals(Status.OK, status);
+        Assertions.assertEquals(1, result.size());
+        Assertions.assertEquals("/test/path/file1", 
result.get(0).getPath().toString());
+    }
+
+    @Test
+    @DisplayName("listFiles should return NOT_FOUND status when 
FileNotFoundException is thrown")
+    void listFilesReturnsNotFoundWhenFileNotFoundExceptionThrown() throws 
Exception {
+
+        mockFileSystem = Mockito.mock(FileSystem.class);
+        String remotePath = "/nonexistent/path";
+        List<RemoteFile> result = new ArrayList<>();
+        
Mockito.doReturn(mockFileSystem).when(remoteFileSystem).nativeFileSystem(remotePath);
+        Mockito.doThrow(new FileNotFoundException("File not found"))
+                .when(mockFileSystem).listFiles(Mockito.any(Path.class), 
Mockito.anyBoolean());
+
+        Status status = remoteFileSystem.listFiles(remotePath, true, result);
+
+        Assertions.assertEquals(Status.ErrCode.NOT_FOUND, status.getErrCode());
+        Assertions.assertTrue(result.isEmpty());
+    }
+
+    @Test
+    @DisplayName("listDirectories should return OK status and populate result 
set with directories")
+    void listDirectoriesReturnsOkWhenDirectoriesFound() throws Exception {
+        String remotePath = "/test/path";
+        Set<String> result = new HashSet<>();
+        FileStatus[] mockFileStatuses = {
+                Mockito.mock(FileStatus.class),
+                Mockito.mock(FileStatus.class)
+        };
+
+        
Mockito.doReturn(mockFileSystem).when(remoteFileSystem).nativeFileSystem(remotePath);
+        Mockito.when(mockFileSystem.listStatus(new 
Path(remotePath))).thenReturn(mockFileStatuses);
+        Mockito.when(mockFileStatuses[0].isDirectory()).thenReturn(true);
+        Mockito.when(mockFileStatuses[0].getPath()).thenReturn(new 
Path("/test/path/dir1"));
+        Mockito.when(mockFileStatuses[1].isDirectory()).thenReturn(false);
+
+        Status status = remoteFileSystem.listDirectories(remotePath, result);
+
+        Assertions.assertEquals(Status.OK, status);
+        Assertions.assertEquals(1, result.size());
+        Assertions.assertTrue(result.contains("/test/path/dir1/"));
+    }
+
+    @Test
+    @DisplayName("renameDir should return COMMON_ERROR when destination 
directory exists")
+    void renameDirReturnsErrorWhenDestinationExists() throws Exception {
+        String origFilePath = "/test/path/orig";
+        String destFilePath = "/test/path/dest";
+
+        Mockito.doReturn(new Status(Status.ErrCode.OK, ""))
+                .when(remoteFileSystem).exists(destFilePath);
+
+        Status status = remoteFileSystem.renameDir(origFilePath, destFilePath, 
() -> {
+        });
+
+        Assertions.assertEquals(Status.ErrCode.COMMON_ERROR, 
status.getErrCode());
+        Assertions.assertEquals("Destination directory already exists: 
/test/path/dest", status.getErrMsg());
+    }
+}


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

Reply via email to