You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by da...@apache.org on 2024/02/26 09:45:57 UTC

(doris) branch master updated: [feature](merge-cloud) Add cloud cluster auth and cloud route (#30784)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new f031b4a607b [feature](merge-cloud) Add cloud cluster auth and cloud route (#30784)
f031b4a607b is described below

commit f031b4a607b6e701b83349c69dce14ba594ba058
Author: deardeng <56...@qq.com>
AuthorDate: Mon Feb 26 17:45:47 2024 +0800

    [feature](merge-cloud) Add cloud cluster auth and cloud route (#30784)
---
 .../org/apache/doris/common/FeMetaVersion.java     |   4 +-
 fe/fe-core/src/main/cup/sql_parser.cup             |  54 +++-
 .../apache/doris/analysis/CreateResourceStmt.java  |   2 +-
 .../apache/doris/analysis/DropResourceStmt.java    |   2 +-
 .../java/org/apache/doris/analysis/GrantStmt.java  |  32 ++-
 .../org/apache/doris/analysis/ResourcePattern.java |  39 ++-
 .../java/org/apache/doris/analysis/RevokeStmt.java |  16 +-
 .../{ShowRolesStmt.java => ShowClusterStmt.java}   |  48 ++--
 .../org/apache/doris/analysis/ShowRolesStmt.java   |   1 +
 .../org/apache/doris/analysis/UserIdentity.java    |   4 +
 .../java/org/apache/doris/catalog/EnvFactory.java  |  21 ++
 .../ranger/doris/RangerDorisAccessController.java  |   7 +
 .../ranger/hive/RangerHiveAccessController.java    |   7 +
 .../doris/cloud/analysis/UseCloudClusterStmt.java  | 113 ++++++++
 .../org/apache/doris/cloud/catalog/CloudEnv.java   |  29 ++
 .../doris/cloud/catalog/CloudEnvFactory.java       |  22 ++
 .../apache/doris/cloud/qe/CloudCoordinator.java    |  93 +++++++
 .../doris/cloud/system/CloudSystemInfoService.java |  80 ++++++
 .../java/org/apache/doris/common/FeNameFormat.java |   9 +-
 .../org/apache/doris/common/proc/AuthProcDir.java  |   3 +-
 .../main/java/org/apache/doris/load/DppConfig.java |   5 +
 .../apache/doris/load/loadv2/LoadLoadingTask.java  |   4 +-
 .../java/org/apache/doris/mysql/MysqlProto.java    |  17 ++
 .../mysql/privilege/AccessControllerManager.java   |  12 +
 .../org/apache/doris/mysql/privilege/Auth.java     |  76 ++++-
 .../mysql/privilege/CatalogAccessController.java   |   4 +
 .../mysql/privilege/InternalAccessController.java  |   7 +
 .../apache/doris/mysql/privilege/PrivBitSet.java   |  43 ++-
 .../doris/mysql/privilege/PrivPredicate.java       |   9 +-
 .../apache/doris/mysql/privilege/Privilege.java    |  68 +++--
 .../org/apache/doris/mysql/privilege/Role.java     | 159 ++++++++++-
 .../apache/doris/mysql/privilege/RoleManager.java  |  22 +-
 .../org/apache/doris/mysql/privilege/User.java     |  16 +-
 .../apache/doris/mysql/privilege/UserManager.java  |  23 ++
 .../apache/doris/mysql/privilege/UserProperty.java |  68 +++--
 .../doris/mysql/privilege/UserPropertyMgr.java     |  50 +++-
 .../trees/plans/commands/InsertExecutor.java       |   3 +-
 .../java/org/apache/doris/persist/PrivInfo.java    |   7 +
 .../org/apache/doris/persist/gson/GsonUtils.java   |   9 -
 .../org/apache/doris/planner/OlapScanNode.java     |   4 +
 .../org/apache/doris/planner/OlapTableSink.java    |  13 +-
 .../java/org/apache/doris/qe/ConnectContext.java   |   9 +-
 .../java/org/apache/doris/qe/ConnectProcessor.java |  14 +
 .../main/java/org/apache/doris/qe/Coordinator.java |   6 +-
 .../java/org/apache/doris/qe/ShowExecutor.java     |  48 ++++
 .../java/org/apache/doris/qe/StmtExecutor.java     |  41 ++-
 .../apache/doris/service/FrontendServiceImpl.java  |   3 +-
 .../org/apache/doris/analysis/GrantStmtTest.java   |   6 +-
 .../apache/doris/datasource/ColumnPrivTest.java    |   6 +
 .../org/apache/doris/mysql/privilege/AuthTest.java |  25 +-
 .../org/apache/doris/persist/PrivInfoTest.java     |   3 +-
 .../apache/doris/planner/ResourceTagQueryTest.java |   2 +-
 .../java/org/apache/doris/qe/CoordinatorTest.java  |  11 +-
 .../account_p0/test_auth_compatibility.groovy      |  91 ++++++
 .../auth/test_grant_revoke_cluster_role.groovy     | 265 ++++++++++++++++++
 .../auth/test_grant_revoke_cluster_to_user.groovy  | 308 +++++++++++++++++++++
 56 files changed, 1866 insertions(+), 177 deletions(-)

diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/FeMetaVersion.java b/fe/fe-common/src/main/java/org/apache/doris/common/FeMetaVersion.java
index 00b40b9fc9f..ad273b3b2a5 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/FeMetaVersion.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/FeMetaVersion.java
@@ -79,8 +79,10 @@ public final class FeMetaVersion {
     // For table version
     public static final int VERSION_129 = 129;
 
+    public static final int VERSION_130 = 130;
+
     // note: when increment meta version, should assign the latest version to VERSION_CURRENT
-    public static final int VERSION_CURRENT = VERSION_129;
+    public static final int VERSION_CURRENT = VERSION_130;
 
     // all logs meta version should >= the minimum version, so that we could remove many if clause, for example
     // if (FE_METAVERSION < VERSION_94) ...
diff --git a/fe/fe-core/src/main/cup/sql_parser.cup b/fe/fe-core/src/main/cup/sql_parser.cup
index 5ad7fa07756..9eb8a9f81ad 100644
--- a/fe/fe-core/src/main/cup/sql_parser.cup
+++ b/fe/fe-core/src/main/cup/sql_parser.cup
@@ -49,6 +49,7 @@ import org.apache.doris.catalog.View;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.FeConstants;
 import org.apache.doris.common.Version;
+import org.apache.doris.cloud.analysis.UseCloudClusterStmt;
 import org.apache.doris.mysql.MysqlPassword;
 import org.apache.doris.load.loadv2.LoadTask;
 import org.apache.doris.policy.PolicyTypeEnum;
@@ -697,7 +698,7 @@ nonterminal StatementBase stmt, show_stmt, show_param, help_stmt, load_stmt,
     show_routine_load_stmt, show_routine_load_task_stmt, show_create_routine_load_stmt, show_create_load_stmt, show_create_reporitory_stmt,
     describe_stmt, alter_stmt, unset_var_stmt,
     create_job_stmt, pause_job_stmt, resume_job_stmt,stop_job_stmt, cancel_job_task_stmt,
-    use_stmt, kill_stmt, drop_stmt, recover_stmt, grant_stmt, revoke_stmt, create_stmt, set_stmt, sync_stmt, cancel_stmt, cancel_param, delete_stmt,
+    use_stmt, use_cloud_cluster_stmt, kill_stmt, drop_stmt, recover_stmt, grant_stmt, revoke_stmt, create_stmt, set_stmt, sync_stmt, cancel_stmt, cancel_param, delete_stmt,
     switch_stmt, transaction_stmt, unsupported_stmt, export_stmt, admin_stmt, truncate_stmt,
     import_columns_stmt, import_delete_on_stmt, import_sequence_stmt, import_where_stmt, install_plugin_stmt, uninstall_plugin_stmt,
     import_preceding_filter_stmt, unlock_tables_stmt, lock_tables_stmt, refresh_stmt, clean_stmt, analyze_stmt, show_mtmv_stmt, kill_analysis_job_stmt, insert_overwrite_stmt;
@@ -1133,6 +1134,8 @@ stmt ::=
     {: RESULT = stmt; :}
     | use_stmt:use
     {: RESULT = use; :}
+    | use_cloud_cluster_stmt:use
+    {: RESULT = use; :}
     | set_stmt:set
     {: RESULT = set; :}
     | unset_var_stmt:stmt
@@ -2874,11 +2877,19 @@ grant_stmt ::=
     :}
     | KW_GRANT privilege_list:privs KW_ON KW_RESOURCE resource_pattern:resourcePattern KW_TO user_identity:userId
     {:
-        RESULT = new GrantStmt(userId, null, resourcePattern, privs);
+        RESULT = new GrantStmt(userId, null, resourcePattern, privs, ResourceTypeEnum.GENERAL);
     :}
     | KW_GRANT privilege_list:privs KW_ON KW_RESOURCE resource_pattern:resourcePattern KW_TO KW_ROLE STRING_LITERAL:role
     {:
-        RESULT = new GrantStmt(null, role, resourcePattern, privs);
+        RESULT = new GrantStmt(null, role, resourcePattern, privs, ResourceTypeEnum.GENERAL);
+    :}
+    | KW_GRANT privilege_list:privs KW_ON KW_CLUSTER resource_pattern:resourcePattern KW_TO user_identity:userId
+    {:
+        RESULT = new GrantStmt(userId, null, resourcePattern, privs, ResourceTypeEnum.CLUSTER);
+    :}
+    | KW_GRANT privilege_list:privs KW_ON KW_CLUSTER resource_pattern:resourcePattern KW_TO KW_ROLE STRING_LITERAL:role
+    {:
+        RESULT = new GrantStmt(null, role, resourcePattern, privs, ResourceTypeEnum.CLUSTER);
     :}
     | KW_GRANT privilege_list:privs KW_ON KW_WORKLOAD KW_GROUP workload_group_pattern:workloadGroupPattern KW_TO user_identity:userId
     {:
@@ -2912,11 +2923,11 @@ tbl_pattern ::=
 resource_pattern ::=
     ident_or_star:resourceName
     {:
-        RESULT = new ResourcePattern(resourceName);
+        RESULT = new ResourcePattern(resourceName, ResourceTypeEnum.GENERAL);
     :}
     | STRING_LITERAL:resourceName
     {:
-        RESULT = new ResourcePattern(resourceName);
+        RESULT = new ResourcePattern(resourceName, ResourceTypeEnum.GENERAL);
     :}
     ;
 
@@ -2954,11 +2965,19 @@ revoke_stmt ::=
     :}
     | KW_REVOKE privilege_list:privs KW_ON KW_RESOURCE resource_pattern:resourcePattern KW_FROM user_identity:userId
     {:
-        RESULT = new RevokeStmt(userId, null, resourcePattern, privs);
+        RESULT = new RevokeStmt(userId, null, resourcePattern, privs, ResourceTypeEnum.GENERAL);
     :}
     | KW_REVOKE privilege_list:privs KW_ON KW_RESOURCE resource_pattern:resourcePattern KW_FROM KW_ROLE STRING_LITERAL:role
     {:
-        RESULT = new RevokeStmt(null, role, resourcePattern, privs);
+        RESULT = new RevokeStmt(null, role, resourcePattern, privs, ResourceTypeEnum.GENERAL);
+    :}
+    | KW_REVOKE privilege_list:privs KW_ON KW_CLUSTER resource_pattern:resourcePattern KW_FROM user_identity:userId
+    {:
+        RESULT = new RevokeStmt(userId, null, resourcePattern, privs, ResourceTypeEnum.CLUSTER);
+    :}
+    | KW_REVOKE privilege_list:privs KW_ON KW_CLUSTER resource_pattern:resourcePattern KW_FROM KW_ROLE STRING_LITERAL:role
+    {:
+        RESULT = new RevokeStmt(null, role, resourcePattern, privs, ResourceTypeEnum.CLUSTER);
     :}
     | KW_REVOKE privilege_list:privs KW_ON KW_WORKLOAD KW_GROUP workload_group_pattern:workloadGroupPattern KW_FROM user_identity:userId
     {:
@@ -4375,6 +4394,11 @@ show_param ::=
     {:
         RESULT = new ShowBuildIndexStmt(db, parser.where, orderByClause, limitClause);
     :}
+    /* Cloud Cluster */
+    | KW_CLUSTERS
+    {:
+        RESULT = new ShowClusterStmt();
+    :}
     | KW_CONVERT_LSC opt_db:db
     {:
         RESULT = new ShowConvertLSCStmt(db);
@@ -4912,6 +4936,22 @@ insert_overwrite_stmt ::=
     :}
     ;
 
+// Change cloud cluster
+use_cloud_cluster_stmt ::=
+    KW_USE AT ident:cluster
+    {:
+        RESULT = new UseCloudClusterStmt(cluster);
+    :}
+    | KW_USE ident:db AT ident:cluster
+    {:
+        RESULT = new UseCloudClusterStmt(cluster, db);
+    :}
+    | KW_USE ident:ctl DOT ident:db AT ident:cluster
+    {:
+        RESULT = new UseCloudClusterStmt(cluster, db, ctl);
+    :}
+    ;
+
 // Insert statement
 insert_stmt ::=
     KW_INSERT KW_INTO insert_target:target opt_with_label:label opt_col_list:cols opt_plan_hints:hints insert_source:source
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateResourceStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateResourceStmt.java
index 9287c430655..58316aa9ec5 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateResourceStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateResourceStmt.java
@@ -77,7 +77,7 @@ public class CreateResourceStmt extends DdlStmt {
         }
 
         // check name
-        FeNameFormat.checkResourceName(resourceName);
+        FeNameFormat.checkResourceName(resourceName, ResourceTypeEnum.GENERAL);
 
         // check type in properties
         if (properties == null || properties.isEmpty()) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/DropResourceStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/DropResourceStmt.java
index 4fdcac6d891..e57c433bb9b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/DropResourceStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/DropResourceStmt.java
@@ -52,7 +52,7 @@ public class DropResourceStmt extends DdlStmt {
             ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN");
         }
 
-        FeNameFormat.checkResourceName(resourceName);
+        FeNameFormat.checkResourceName(resourceName, ResourceTypeEnum.GENERAL);
     }
 
     @Override
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
index eb8ce67fd69..f141afa63c2 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/GrantStmt.java
@@ -27,6 +27,7 @@ import org.apache.doris.common.FeNameFormat;
 import org.apache.doris.common.UserException;
 import org.apache.doris.mysql.privilege.Auth.PrivLevel;
 import org.apache.doris.mysql.privilege.ColPrivilegeKey;
+import org.apache.doris.mysql.privilege.PrivBitSet;
 import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.mysql.privilege.Privilege;
 import org.apache.doris.qe.ConnectContext;
@@ -66,17 +67,17 @@ public class GrantStmt extends DdlStmt {
 
     public GrantStmt(UserIdentity userIdent, String role, TablePattern tblPattern,
             List<AccessPrivilegeWithCols> privileges) {
-        this(userIdent, role, tblPattern, null, null, privileges);
+        this(userIdent, role, tblPattern, null, null, privileges, ResourceTypeEnum.GENERAL);
     }
 
     public GrantStmt(UserIdentity userIdent, String role,
-            ResourcePattern resourcePattern, List<AccessPrivilegeWithCols> privileges) {
-        this(userIdent, role, null, resourcePattern, null, privileges);
+            ResourcePattern resourcePattern, List<AccessPrivilegeWithCols> privileges, ResourceTypeEnum type) {
+        this(userIdent, role, null, resourcePattern, null, privileges, type);
     }
 
     public GrantStmt(UserIdentity userIdent, String role,
             WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilegeWithCols> privileges) {
-        this(userIdent, role, null, null, workloadGroupPattern, privileges);
+        this(userIdent, role, null, null, workloadGroupPattern, privileges, ResourceTypeEnum.GENERAL);
     }
 
     public GrantStmt(List<String> roles, UserIdentity userIdent) {
@@ -85,12 +86,16 @@ public class GrantStmt extends DdlStmt {
     }
 
     private GrantStmt(UserIdentity userIdent, String role, TablePattern tblPattern, ResourcePattern resourcePattern,
-            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilegeWithCols> accessPrivileges) {
+            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilegeWithCols> accessPrivileges,
+                      ResourceTypeEnum type) {
         this.userIdent = userIdent;
         this.role = role;
         this.tblPattern = tblPattern;
         this.resourcePattern = resourcePattern;
         this.workloadGroupPattern = workloadGroupPattern;
+        if (this.resourcePattern != null) {
+            this.resourcePattern.setResourceType(type);
+        }
         this.accessPrivileges = accessPrivileges;
     }
 
@@ -167,6 +172,7 @@ public class GrantStmt extends DdlStmt {
         if (tblPattern != null) {
             checkTablePrivileges(privileges, role, tblPattern, colPrivileges);
         } else if (resourcePattern != null) {
+            privileges = PrivBitSet.convertResourcePrivToCloudPriv(resourcePattern, privileges);
             checkResourcePrivileges(privileges, role, resourcePattern);
         } else if (workloadGroupPattern != null) {
             checkWorkloadGroupPrivileges(privileges, role, workloadGroupPattern);
@@ -251,7 +257,7 @@ public class GrantStmt extends DdlStmt {
         }
 
         // Rule 6
-        if (privileges.contains(Privilege.USAGE_PRIV)) {
+        if (privileges.contains(Privilege.USAGE_PRIV) || privileges.contains(Privilege.CLUSTER_USAGE_PRIV)) {
             throw new AnalysisException("Can not grant/revoke USAGE_PRIV to/from database or table");
         }
 
@@ -284,9 +290,17 @@ public class GrantStmt extends DdlStmt {
                     ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
                 }
             } else {
-                if (!Env.getCurrentEnv().getAccessManager().checkResourcePriv(ConnectContext.get(),
-                        resourcePattern.getResourceName(), PrivPredicate.GRANT)) {
-                    ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
+                if (resourcePattern.isGeneralResource()) {
+                    if (!Env.getCurrentEnv().getAccessManager().checkResourcePriv(ConnectContext.get(),
+                            resourcePattern.getResourceName(), PrivPredicate.GRANT)) {
+                        ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
+                    }
+                } else if (resourcePattern.isClusterResource()) {
+                    if (!Env.getCurrentEnv().getAccessManager()
+                            .checkCloudPriv(ConnectContext.get(),
+                            resourcePattern.getResourceName(), PrivPredicate.GRANT, ResourceTypeEnum.CLUSTER)) {
+                        ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT/ROVOKE");
+                    }
                 }
             }
         }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/ResourcePattern.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/ResourcePattern.java
index 2e9b203c170..6afeae3f4b8 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/ResourcePattern.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/ResourcePattern.java
@@ -38,12 +38,24 @@ public class ResourcePattern implements Writable {
     @SerializedName(value = "resourceName")
     private String resourceName;
 
-    public static ResourcePattern ALL;
+    // just for cloud
+    // GRANT USAGE_PRIV ON CLUSTER '${clusterName}' TO '${userName}';
+    @SerializedName(value = "resourceType")
+    private ResourceTypeEnum resourceType;
+
+    public static ResourcePattern ALL_GENERAL;
+    public static ResourcePattern ALL_CLUSTER;
+    public static ResourcePattern ALL_STAGE;
 
     static {
-        ALL = new ResourcePattern("*");
+        ALL_GENERAL = new ResourcePattern("*", ResourceTypeEnum.GENERAL);
+        ALL_CLUSTER = new ResourcePattern("*", ResourceTypeEnum.CLUSTER);
+        ALL_STAGE = new ResourcePattern("*", ResourceTypeEnum.STAGE);
+
         try {
-            ALL.analyze();
+            ALL_GENERAL.analyze();
+            ALL_CLUSTER.analyze();
+            ALL_STAGE.analyze();
         } catch (AnalysisException e) {
             // will not happen
         }
@@ -52,8 +64,21 @@ public class ResourcePattern implements Writable {
     private ResourcePattern() {
     }
 
-    public ResourcePattern(String resourceName) {
+    public ResourcePattern(String resourceName, ResourceTypeEnum type) {
         this.resourceName = Strings.isNullOrEmpty(resourceName) ? "*" : resourceName;
+        resourceType = type;
+    }
+
+    public void setResourceType(ResourceTypeEnum type) {
+        resourceType = type;
+    }
+
+    public boolean isGeneralResource() {
+        return resourceType == ResourceTypeEnum.GENERAL;
+    }
+
+    public boolean isClusterResource() {
+        return resourceType == ResourceTypeEnum.CLUSTER;
     }
 
     public String getResourceName() {
@@ -70,7 +95,7 @@ public class ResourcePattern implements Writable {
 
     public void analyze() throws AnalysisException {
         if (!resourceName.equals("*")) {
-            FeNameFormat.checkResourceName(resourceName);
+            FeNameFormat.checkResourceName(resourceName, resourceType);
         }
     }
 
@@ -80,13 +105,13 @@ public class ResourcePattern implements Writable {
             return false;
         }
         ResourcePattern other = (ResourcePattern) obj;
-        return resourceName.equals(other.getResourceName());
+        return resourceName.equals(other.getResourceName()) && resourceType.equals(other.resourceType);
     }
 
     @Override
     public int hashCode() {
         int result = 17;
-        result = 31 * result + resourceName.hashCode();
+        result = 31 * result + resourceName.hashCode() + resourceType.hashCode();
         return result;
     }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java
index 18066b925da..70a44a4625c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/RevokeStmt.java
@@ -21,6 +21,7 @@ import org.apache.doris.catalog.AccessPrivilegeWithCols;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.FeNameFormat;
 import org.apache.doris.mysql.privilege.ColPrivilegeKey;
+import org.apache.doris.mysql.privilege.PrivBitSet;
 import org.apache.doris.mysql.privilege.Privilege;
 
 import com.google.common.base.Joiner;
@@ -54,17 +55,17 @@ public class RevokeStmt extends DdlStmt {
 
     public RevokeStmt(UserIdentity userIdent, String role, TablePattern tblPattern,
             List<AccessPrivilegeWithCols> privileges) {
-        this(userIdent, role, tblPattern, null, null, privileges);
+        this(userIdent, role, tblPattern, null, null, privileges, ResourceTypeEnum.GENERAL);
     }
 
     public RevokeStmt(UserIdentity userIdent, String role,
-            ResourcePattern resourcePattern, List<AccessPrivilegeWithCols> privileges) {
-        this(userIdent, role, null, resourcePattern, null, privileges);
+            ResourcePattern resourcePattern, List<AccessPrivilegeWithCols> privileges, ResourceTypeEnum type) {
+        this(userIdent, role, null, resourcePattern, null, privileges, type);
     }
 
     public RevokeStmt(UserIdentity userIdent, String role,
             WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilegeWithCols> privileges) {
-        this(userIdent, role, null, null, workloadGroupPattern, privileges);
+        this(userIdent, role, null, null, workloadGroupPattern, privileges, ResourceTypeEnum.GENERAL);
     }
 
     public RevokeStmt(List<String> roles, UserIdentity userIdent) {
@@ -73,12 +74,16 @@ public class RevokeStmt extends DdlStmt {
     }
 
     private RevokeStmt(UserIdentity userIdent, String role, TablePattern tblPattern, ResourcePattern resourcePattern,
-            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilegeWithCols> accessPrivileges) {
+            WorkloadGroupPattern workloadGroupPattern, List<AccessPrivilegeWithCols> accessPrivileges,
+                       ResourceTypeEnum type) {
         this.userIdent = userIdent;
         this.role = role;
         this.tblPattern = tblPattern;
         this.resourcePattern = resourcePattern;
         this.workloadGroupPattern = workloadGroupPattern;
+        if (this.resourcePattern != null) {
+            this.resourcePattern.setResourceType(type);
+        }
         this.accessPrivileges = accessPrivileges;
     }
 
@@ -149,6 +154,7 @@ public class RevokeStmt extends DdlStmt {
         if (tblPattern != null) {
             GrantStmt.checkTablePrivileges(privileges, role, tblPattern, colPrivileges);
         } else if (resourcePattern != null) {
+            privileges = PrivBitSet.convertResourcePrivToCloudPriv(resourcePattern, privileges);
             GrantStmt.checkResourcePrivileges(privileges, role, resourcePattern);
         } else if (workloadGroupPattern != null) {
             GrantStmt.checkWorkloadGroupPrivileges(privileges, role, workloadGroupPattern);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowClusterStmt.java
similarity index 52%
copy from fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java
copy to fe/fe-core/src/main/java/org/apache/doris/analysis/ShowClusterStmt.java
index fdeaeaea23d..50bd36b1882 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowClusterStmt.java
@@ -17,47 +17,61 @@
 
 package org.apache.doris.analysis;
 
+import org.apache.doris.analysis.CompoundPredicate.Operator;
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.ScalarType;
 import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.Config;
 import org.apache.doris.common.ErrorCode;
 import org.apache.doris.common.ErrorReport;
+import org.apache.doris.mysql.privilege.PrivBitSet;
 import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.mysql.privilege.Privilege;
 import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.qe.ShowResultSetMetaData;
 
-public class ShowRolesStmt extends ShowStmt {
-    private static final ShowResultSetMetaData META_DATA;
+import com.google.common.collect.ImmutableList;
 
-    static {
+public class ShowClusterStmt extends ShowStmt {
+    public static final ImmutableList<String> TITLE_NAMES = new ImmutableList.Builder<String>()
+            .add("cluster").add("is_current").add("users").build();
+
+    public ShowClusterStmt() {
+    }
+
+    @Override
+    public ShowResultSetMetaData getMetaData() {
         ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder();
 
-        builder.addColumn(new Column("Name", ScalarType.createVarchar(100)));
-        builder.addColumn(new Column("Users", ScalarType.createVarchar(100)));
-        builder.addColumn(new Column("GlobalPrivs", ScalarType.createVarchar(300)));
-        builder.addColumn(new Column("CatalogPrivs", ScalarType.createVarchar(300)));
-        builder.addColumn(new Column("DatabasePrivs", ScalarType.createVarchar(300)));
-        builder.addColumn(new Column("TablePrivs", ScalarType.createVarchar(300)));
-        builder.addColumn(new Column("ResourcePrivs", ScalarType.createVarchar(300)));
+        ImmutableList<String> titleNames = null;
+        titleNames = TITLE_NAMES;
 
-        META_DATA = builder.build();
+        for (String title : titleNames) {
+            builder.addColumn(new Column(title, ScalarType.createVarchar(128)));
+        }
+        return builder.build();
     }
 
-    public ShowRolesStmt() {
-
+    @Override
+    public SelectStmt toSelectStmt(Analyzer analyzer) throws AnalysisException {
+        return null;
     }
 
     @Override
     public void analyze(Analyzer analyzer) throws AnalysisException {
-        if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
-            ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT");
+        if (Config.isNotCloudMode()) {
+            // just user admin
+            if (!Env.getCurrentEnv().getAuth().checkGlobalPriv(ConnectContext.get().getCurrentUserIdentity(),
+                        PrivPredicate.of(PrivBitSet.of(Privilege.ADMIN_PRIV, Privilege.NODE_PRIV), Operator.OR))) {
+                ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN");
+            }
         }
     }
 
     @Override
-    public ShowResultSetMetaData getMetaData() {
-        return META_DATA;
+    public String toSql() {
+        return super.toSql();
     }
 
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java
index fdeaeaea23d..09b35d79414 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/ShowRolesStmt.java
@@ -40,6 +40,7 @@ public class ShowRolesStmt extends ShowStmt {
         builder.addColumn(new Column("DatabasePrivs", ScalarType.createVarchar(300)));
         builder.addColumn(new Column("TablePrivs", ScalarType.createVarchar(300)));
         builder.addColumn(new Column("ResourcePrivs", ScalarType.createVarchar(300)));
+        builder.addColumn(new Column("CloudClusterPrivs", ScalarType.createVarchar(300)));
 
         META_DATA = builder.build();
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java
index 76444b9f272..122f28ca450 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/UserIdentity.java
@@ -112,6 +112,10 @@ public class UserIdentity implements Writable, GsonPostProcessable {
         return user;
     }
 
+    public String getUser() {
+        return user;
+    }
+
     public String getHost() {
         return host;
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/EnvFactory.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/EnvFactory.java
index cab1b0f1100..f8a73f963d9 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/EnvFactory.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/EnvFactory.java
@@ -17,7 +17,9 @@
 
 package org.apache.doris.catalog;
 
+import org.apache.doris.analysis.Analyzer;
 import org.apache.doris.analysis.BrokerDesc;
+import org.apache.doris.analysis.DescriptorTable;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.cloud.catalog.CloudEnvFactory;
 import org.apache.doris.common.Config;
@@ -25,12 +27,20 @@ import org.apache.doris.common.MetaNotFoundException;
 import org.apache.doris.common.util.PropertyAnalyzer;
 import org.apache.doris.datasource.InternalCatalog;
 import org.apache.doris.load.loadv2.BrokerLoadJob;
+import org.apache.doris.nereids.stats.StatsErrorEstimator;
+import org.apache.doris.planner.PlanFragment;
+import org.apache.doris.planner.Planner;
+import org.apache.doris.planner.ScanNode;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.Coordinator;
 import org.apache.doris.qe.OriginStatement;
 import org.apache.doris.system.SystemInfoService;
+import org.apache.doris.thrift.TUniqueId;
 import org.apache.doris.transaction.GlobalTransactionMgr;
 import org.apache.doris.transaction.GlobalTransactionMgrIface;
 
 import java.lang.reflect.Type;
+import java.util.List;
 import java.util.Map;
 
 public class EnvFactory {
@@ -106,4 +116,15 @@ public class EnvFactory {
     public BrokerLoadJob createBrokerLoadJob() {
         return new BrokerLoadJob();
     }
+
+    public Coordinator createCoordinator(ConnectContext context, Analyzer analyzer, Planner planner,
+                                         StatsErrorEstimator statsErrorEstimator) {
+        return new Coordinator(context, analyzer, planner, statsErrorEstimator);
+    }
+
+    public Coordinator createCoordinator(Long jobId, TUniqueId queryId, DescriptorTable descTable,
+                                         List<PlanFragment> fragments, List<ScanNode> scanNodes,
+                                         String timezone, boolean loadZeroTolerance) {
+        return new Coordinator(jobId, queryId, descTable, fragments, scanNodes, timezone, loadZeroTolerance);
+    }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java
index 1a4c7aa6b5a..2f9ef5b8210 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/doris/RangerDorisAccessController.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.catalog.authorizer.ranger.doris;
 
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.authorizer.ranger.RangerAccessController;
@@ -156,6 +157,12 @@ public class RangerDorisAccessController extends RangerAccessController {
         checkPrivileges(currentUser, DorisAccessType.toAccessType(wanted), resources);
     }
 
+    @Override
+    public boolean checkCloudPriv(UserIdentity currentUser, String resourceName,
+                                  PrivPredicate wanted, ResourceTypeEnum type) {
+        return false;
+    }
+
     @Override
     public boolean checkResourcePriv(UserIdentity currentUser, String resourceName, PrivPredicate wanted) {
         RangerDorisResource resource = new RangerDorisResource(DorisObjectType.RESOURCE, resourceName);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java
index b7016263178..3e7d3a89cd7 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.catalog.authorizer.ranger.hive;
 
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.authorizer.ranger.RangerAccessController;
@@ -184,6 +185,12 @@ public class RangerHiveAccessController extends RangerAccessController {
         checkPrivileges(currentUser, convertToAccessType(wanted), resources);
     }
 
+    @Override
+    public boolean checkCloudPriv(UserIdentity currentUser, String resourceName,
+                                  PrivPredicate wanted, ResourceTypeEnum type) {
+        return false;
+    }
+
     @Override
     public boolean checkResourcePriv(UserIdentity currentUser, String resourceName, PrivPredicate wanted) {
         return false;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/cloud/analysis/UseCloudClusterStmt.java b/fe/fe-core/src/main/java/org/apache/doris/cloud/analysis/UseCloudClusterStmt.java
new file mode 100644
index 00000000000..81954b4a257
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/analysis/UseCloudClusterStmt.java
@@ -0,0 +1,113 @@
+// 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.cloud.analysis;
+
+import org.apache.doris.analysis.Analyzer;
+import org.apache.doris.analysis.RedirectStatus;
+import org.apache.doris.analysis.ResourceTypeEnum;
+import org.apache.doris.analysis.StatementBase;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.cloud.system.CloudSystemInfoService;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.common.UserException;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.qe.ConnectContext;
+
+import com.google.common.base.Strings;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Representation of a use cluster statement.
+ */
+public class UseCloudClusterStmt extends StatementBase {
+    private static final Logger LOG = LogManager.getLogger(UseCloudClusterStmt.class);
+    private String cluster;
+    private String database;
+    private String catalogName;
+
+    public UseCloudClusterStmt(String cluster) {
+        this.cluster = cluster;
+    }
+
+    public UseCloudClusterStmt(String cluster, String db) {
+        this.cluster = cluster;
+        this.database = db;
+    }
+
+    public UseCloudClusterStmt(String cluster, String db, String catalogName) {
+        this.cluster = cluster;
+        this.database = db;
+        this.catalogName = catalogName;
+    }
+
+    public String getCluster() {
+        return cluster;
+    }
+
+    public String getDatabase() {
+        return database;
+    }
+
+    public String getCatalogName() {
+        return catalogName;
+    }
+
+    @Override
+    public String toSql() {
+        return "USE `" + cluster + "`";
+    }
+
+    @Override
+    public String toString() {
+        return toSql();
+    }
+
+    public void analyze(Analyzer analyzer) throws AnalysisException, UserException {
+        super.analyze(analyzer);
+        // check resource usage privilege
+        if (Strings.isNullOrEmpty(cluster)) {
+            ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_CLUSTER_ERROR);
+        }
+        if (!Env.getCurrentEnv().getAuth().checkCloudPriv(ConnectContext.get().getCurrentUserIdentity(),
+                cluster, PrivPredicate.USAGE, ResourceTypeEnum.CLUSTER)) {
+            throw new AnalysisException("USAGE denied to user '" + ConnectContext.get().getQualifiedUser()
+                + "'@'" + ConnectContext.get().getRemoteIP()
+                + "' for cloud cluster '" + cluster + "'");
+        }
+
+        if (!((CloudSystemInfoService) Env.getCurrentSystemInfo()).getCloudClusterNames().contains(cluster)) {
+            ErrorReport.reportAnalysisException(ErrorCode.ERR_CLOUD_CLUSTER_ERROR, cluster);
+        }
+
+        if (Strings.isNullOrEmpty(database)) {
+            return;
+        }
+        if (!Env.getCurrentEnv().getAccessManager().checkDbPriv(ConnectContext.get(), database, PrivPredicate.SHOW)) {
+            ErrorReport.reportAnalysisException(ErrorCode.ERR_DBACCESS_DENIED_ERROR,
+                    analyzer.getQualifiedUser(), database);
+        }
+    }
+
+    @Override
+    public RedirectStatus getRedirectStatus() {
+        return RedirectStatus.NO_FORWARD;
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java
index 1320137ab40..00aef304cf3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java
@@ -25,6 +25,7 @@ import org.apache.doris.cloud.system.CloudSystemInfoService;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.UserException;
 import org.apache.doris.common.io.CountingDataOutputStream;
 import org.apache.doris.common.util.HttpURLUtil;
 import org.apache.doris.common.util.NetUtils;
@@ -378,4 +379,32 @@ public class CloudEnv extends Env {
                 ErrorCode.ERR_CLOUD_CLUSTER_ERROR);
         }
     }
+
+    public void changeCloudCluster(String clusterName, ConnectContext ctx) throws DdlException {
+        checkCloudClusterPriv(clusterName);
+        // TODO(merge-cloud): pick cloud auto start
+        // waitForAutoStart(clusterName);
+        try {
+            ((CloudSystemInfoService) Env.getCurrentSystemInfo()).addCloudCluster(clusterName, "");
+        } catch (UserException e) {
+            throw new DdlException(e.getMessage(), e.getMysqlErrorCode());
+        }
+        ctx.setCloudCluster(clusterName);
+        ctx.getState().setOk();
+    }
+
+    public String analyzeCloudCluster(String name, ConnectContext ctx) throws DdlException {
+        String[] res = name.split("@");
+        if (res.length != 1 && res.length != 2) {
+            LOG.warn("invalid database name {}", name);
+            throw new DdlException("Invalid database name: " + name, ErrorCode.ERR_BAD_DB_ERROR);
+        }
+
+        if (res.length == 1) {
+            return name;
+        }
+
+        changeCloudCluster(res[1], ctx);
+        return res[0];
+    }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnvFactory.java b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnvFactory.java
index fbab0a2a9a8..9e9934db9cb 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnvFactory.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnvFactory.java
@@ -17,7 +17,9 @@
 
 package org.apache.doris.cloud.catalog;
 
+import org.apache.doris.analysis.Analyzer;
 import org.apache.doris.analysis.BrokerDesc;
+import org.apache.doris.analysis.DescriptorTable;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.DynamicPartitionProperty;
 import org.apache.doris.catalog.Env;
@@ -29,17 +31,26 @@ import org.apache.doris.catalog.Tablet;
 import org.apache.doris.cloud.common.util.CloudPropertyAnalyzer;
 import org.apache.doris.cloud.datasource.CloudInternalCatalog;
 import org.apache.doris.cloud.load.CloudBrokerLoadJob;
+import org.apache.doris.cloud.qe.CloudCoordinator;
 import org.apache.doris.cloud.system.CloudSystemInfoService;
 import org.apache.doris.cloud.transaction.CloudGlobalTransactionMgr;
 import org.apache.doris.common.MetaNotFoundException;
 import org.apache.doris.common.util.PropertyAnalyzer;
 import org.apache.doris.datasource.InternalCatalog;
 import org.apache.doris.load.loadv2.BrokerLoadJob;
+import org.apache.doris.nereids.stats.StatsErrorEstimator;
+import org.apache.doris.planner.PlanFragment;
+import org.apache.doris.planner.Planner;
+import org.apache.doris.planner.ScanNode;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.Coordinator;
 import org.apache.doris.qe.OriginStatement;
 import org.apache.doris.system.SystemInfoService;
+import org.apache.doris.thrift.TUniqueId;
 import org.apache.doris.transaction.GlobalTransactionMgrIface;
 
 import java.lang.reflect.Type;
+import java.util.List;
 import java.util.Map;
 
 public class CloudEnvFactory extends EnvFactory {
@@ -122,4 +133,15 @@ public class CloudEnvFactory extends EnvFactory {
     public BrokerLoadJob createBrokerLoadJob() {
         return new CloudBrokerLoadJob();
     }
+
+    public Coordinator createCoordinator(ConnectContext context, Analyzer analyzer, Planner planner,
+                                         StatsErrorEstimator statsErrorEstimator) {
+        return new CloudCoordinator(context, analyzer, planner, statsErrorEstimator);
+    }
+
+    public Coordinator createCoordinator(Long jobId, TUniqueId queryId, DescriptorTable descTable,
+                                         List<PlanFragment> fragments, List<ScanNode> scanNodes,
+                                         String timezone, boolean loadZeroTolerance) {
+        return new CloudCoordinator(jobId, queryId, descTable, fragments, scanNodes, timezone, loadZeroTolerance);
+    }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/cloud/qe/CloudCoordinator.java b/fe/fe-core/src/main/java/org/apache/doris/cloud/qe/CloudCoordinator.java
new file mode 100644
index 00000000000..c896533cc09
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/qe/CloudCoordinator.java
@@ -0,0 +1,93 @@
+// 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.cloud.qe;
+
+import org.apache.doris.analysis.Analyzer;
+import org.apache.doris.analysis.DescriptorTable;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.cloud.catalog.CloudEnv;
+import org.apache.doris.cloud.system.CloudSystemInfoService;
+import org.apache.doris.nereids.stats.StatsErrorEstimator;
+import org.apache.doris.planner.PlanFragment;
+import org.apache.doris.planner.Planner;
+import org.apache.doris.planner.ScanNode;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.Coordinator;
+import org.apache.doris.thrift.TUniqueId;
+
+import com.google.common.base.Strings;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.List;
+
+public class CloudCoordinator extends Coordinator {
+    private static final Logger LOG = LogManager.getLogger(Coordinator.class);
+
+    public CloudCoordinator(ConnectContext context, Analyzer analyzer,
+                            Planner planner, StatsErrorEstimator statsErrorEstimator) {
+        super(context, analyzer, planner, statsErrorEstimator);
+    }
+
+    public CloudCoordinator(Long jobId, TUniqueId queryId, DescriptorTable descTable, List<PlanFragment> fragments,
+                       List<ScanNode> scanNodes, String timezone, boolean loadZeroTolerance) {
+        super(jobId, queryId, descTable, fragments, scanNodes, timezone, loadZeroTolerance);
+    }
+
+    protected void prepare() {
+        String cluster = null;
+        ConnectContext context = ConnectContext.get();
+        if (context != null) {
+            if (!Strings.isNullOrEmpty(context.getSessionVariable().getCloudCluster())) {
+                cluster = context.getSessionVariable().getCloudCluster();
+                try {
+                    ((CloudEnv) Env.getCurrentEnv()).checkCloudClusterPriv(cluster);
+                } catch (Exception e) {
+                    LOG.warn("get cluster by session context exception", e);
+                    return;
+                }
+                LOG.debug("get cluster by session context cluster: {}", cluster);
+            } else {
+                cluster = context.getCloudCluster();
+                LOG.debug("get cluster by context {}", cluster);
+            }
+        } else {
+            LOG.warn("connect context is null in coordinator prepare");
+            return;
+        }
+
+        if (Strings.isNullOrEmpty(cluster)) {
+            LOG.warn("invalid clusterName: {}", cluster);
+            return;
+        }
+
+        this.idToBackend = ((CloudSystemInfoService) Env.getCurrentSystemInfo()).getCloudIdToBackend(cluster);
+        super.prepare();
+    }
+
+    protected void processFragmentAssignmentAndParams() throws Exception {
+        super.processFragmentAssignmentAndParams();
+        if (idToBackend == null || idToBackend.isEmpty()) {
+            LOG.warn("no available backends, idToBackend {}", idToBackend);
+            String clusterName = ConnectContext.get() != null
+                    ? ConnectContext.get().getCloudCluster() : "ctx empty cant get clusterName";
+            throw new Exception("no available backends, the cluster maybe not be set or been dropped clusterName = "
+                + clusterName);
+        }
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/cloud/system/CloudSystemInfoService.java b/fe/fe-core/src/main/java/org/apache/doris/cloud/system/CloudSystemInfoService.java
index e90c3b39777..d7682d5d987 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/cloud/system/CloudSystemInfoService.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/system/CloudSystemInfoService.java
@@ -27,6 +27,7 @@ import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.FeConstants;
 import org.apache.doris.common.Pair;
+import org.apache.doris.common.UserException;
 import org.apache.doris.ha.FrontendNodeType;
 import org.apache.doris.metric.MetricRepo;
 import org.apache.doris.resource.Tag;
@@ -376,6 +377,83 @@ public class CloudSystemInfoService extends SystemInfoService {
         return new ArrayList<>(clusterNameToId.keySet());
     }
 
+    // use cluster $clusterName
+    // return clusterName for userName
+    public String addCloudCluster(final String clusterName, final String userName) throws UserException {
+        lock.lock();
+        if ((Strings.isNullOrEmpty(clusterName) && Strings.isNullOrEmpty(userName))
+                || (!Strings.isNullOrEmpty(clusterName) && !Strings.isNullOrEmpty(userName))) {
+            // clusterName or userName just only need one.
+            lock.unlock();
+            LOG.warn("addCloudCluster args err clusterName {}, userName {}", clusterName, userName);
+            return "";
+        }
+        // First time this method is called, build cloud cluster map
+        if (clusterNameToId.isEmpty() || clusterIdToBackend.isEmpty()) {
+            List<Backend> toAdd = Maps.newHashMap(idToBackendRef)
+                    .values().stream()
+                    .filter(i -> i.getTagMap().containsKey(Tag.CLOUD_CLUSTER_ID))
+                    .filter(i -> i.getTagMap().containsKey(Tag.CLOUD_CLUSTER_NAME))
+                    .collect(Collectors.toList());
+            // The larger bakendId the later it was added, the order matters
+            toAdd.sort((x, y) -> (int) (x.getId() - y.getId()));
+            updateCloudClusterMap(toAdd, new ArrayList<>());
+        }
+
+        String clusterId;
+        if (Strings.isNullOrEmpty(userName)) {
+            // use clusterName
+            LOG.info("try to add a cloud cluster, clusterName={}", clusterName);
+            clusterId = clusterNameToId.get(clusterName);
+            clusterId = clusterId == null ? "" : clusterId;
+            if (clusterIdToBackend.containsKey(clusterId)) { // Cluster already added
+                lock.unlock();
+                LOG.info("cloud cluster already added, clusterName={}, clusterId={}", clusterName, clusterId);
+                return "";
+            }
+        }
+        lock.unlock();
+        LOG.info("begin to get cloud cluster from remote, clusterName={}, userName={}", clusterName, userName);
+
+        // Get cloud cluster info from resource manager
+        Cloud.GetClusterResponse response = getCloudCluster(clusterName, "", userName);
+        if (!response.hasStatus() || !response.getStatus().hasCode()
+                || response.getStatus().getCode() != Cloud.MetaServiceCode.OK) {
+            LOG.warn("get cluster info from meta failed, clusterName={}, incomplete response: {}",
+                    clusterName, response);
+            throw new UserException("no cluster clusterName: " + clusterName + " or userName: " + userName + " found");
+        }
+
+        // Note: get_cluster interface cluster(option -> repeated), so it has at least one cluster.
+        if (response.getClusterCount() == 0) {
+            LOG.warn("meta service error , return cluster zero, plz check it, "
+                    + "cloud_unique_id={}, clusterId={}, response={}",
+                    Config.cloud_unique_id, Config.cloud_sql_server_cluster_id, response);
+            throw new UserException("get cluster return zero cluster info");
+        }
+
+        ClusterPB cpb = response.getCluster(0);
+        clusterId = cpb.getClusterId();
+        String clusterNameMeta = cpb.getClusterName();
+
+        // Prepare backends
+        Map<String, String> newTagMap = Tag.DEFAULT_BACKEND_TAG.toMap();
+        newTagMap.put(Tag.CLOUD_CLUSTER_NAME, clusterNameMeta);
+        newTagMap.put(Tag.CLOUD_CLUSTER_ID, clusterId);
+        List<Backend> backends = new ArrayList<>();
+        for (Cloud.NodeInfoPB node : cpb.getNodesList()) {
+            Backend b = new Backend(Env.getCurrentEnv().getNextId(), node.getIp(), node.getHeartbeatPort());
+            b.setTagMap(newTagMap);
+            backends.add(b);
+            LOG.info("new backend to add, clusterName={} clusterId={} backend={}",
+                    clusterNameMeta, clusterId, b.toString());
+        }
+
+        updateCloudBackends(backends, new ArrayList<>());
+        return clusterNameMeta;
+    }
+
+
     // Return the ref of concurrentMap clusterIdToBackend
     // It should be thread-safe to iterate.
     // reference: https://stackoverflow.com/questions/3768554/is-iterating-concurrenthashmap-values-thread-safe
@@ -457,4 +535,6 @@ public class CloudSystemInfoService extends SystemInfoService {
         // TODO: merge from cloud.
         throw new DdlException("Env.waitForAutoStart unimplemented");
     }
+
+
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/FeNameFormat.java b/fe/fe-core/src/main/java/org/apache/doris/common/FeNameFormat.java
index d73e78cedce..03d7575df7b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/FeNameFormat.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/FeNameFormat.java
@@ -19,6 +19,7 @@ package org.apache.doris.common;
 
 import org.apache.doris.alter.SchemaChangeHandler;
 import org.apache.doris.analysis.CreateMaterializedViewStmt;
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.datasource.InternalCatalog;
 import org.apache.doris.mysql.privilege.Role;
 import org.apache.doris.mysql.privilege.RoleManager;
@@ -126,8 +127,12 @@ public class FeNameFormat {
         }
     }
 
-    public static void checkResourceName(String resourceName) throws AnalysisException {
-        checkCommonName("resource", resourceName);
+    public static void checkResourceName(String resourceName, ResourceTypeEnum type) throws AnalysisException {
+        if (type == ResourceTypeEnum.GENERAL) {
+            checkCommonName("resource", resourceName);
+        } else {
+            checkCommonName("clusterName", resourceName);
+        }
     }
 
     public static void checkWorkloadGroupName(String workloadGroupName) throws AnalysisException {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java b/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
index 6adc86ee269..66da0601092 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/common/proc/AuthProcDir.java
@@ -32,7 +32,8 @@ import com.google.common.collect.ImmutableList;
 public class AuthProcDir implements ProcDirInterface {
     public static final ImmutableList<String> TITLE_NAMES = new ImmutableList.Builder<String>()
             .add("UserIdentity").add("Password").add("Roles").add("GlobalPrivs").add("CatalogPrivs")
-            .add("DatabasePrivs").add("TablePrivs").add("ColPrivs").add("ResourcePrivs").add("WorkloadGroupPrivs")
+            .add("DatabasePrivs").add("TablePrivs").add("ColPrivs").add("ResourcePrivs").add("CloudClusterPrivs")
+            .add("WorkloadGroupPrivs")
             .build();
 
     private Auth auth;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/load/DppConfig.java b/fe/fe-core/src/main/java/org/apache/doris/load/DppConfig.java
index 5b15ced9fbb..1a4c01bf8fc 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/load/DppConfig.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/load/DppConfig.java
@@ -27,6 +27,7 @@ import org.apache.doris.thrift.TPriority;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.Maps;
+import com.google.gson.annotations.SerializedName;
 
 import java.io.DataInput;
 import java.io.DataOutput;
@@ -64,12 +65,16 @@ public class DppConfig implements Writable {
     // palo base path in hadoop
     //   dpp: paloPath/cluster_id/applications/dpp_version
     //   output: paloPath/cluster_id/output
+    @SerializedName(value = "paloPath")
     private String paloPath;
+    @SerializedName(value = "httpPort")
     private int httpPort;
+    @SerializedName(value = "hadoopConfigs")
     private Map<String, String> hadoopConfigs;
 
     // priority for palo internal schedule
     // for now are etl submit schedule and download file schedule
+    @SerializedName(value = "priority")
     private TPriority priority;
 
     // for persist
diff --git a/fe/fe-core/src/main/java/org/apache/doris/load/loadv2/LoadLoadingTask.java b/fe/fe-core/src/main/java/org/apache/doris/load/loadv2/LoadLoadingTask.java
index 8fbabea8629..c389fe877d0 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/load/loadv2/LoadLoadingTask.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/load/loadv2/LoadLoadingTask.java
@@ -20,6 +20,7 @@ package org.apache.doris.load.loadv2;
 import org.apache.doris.analysis.BrokerDesc;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.Database;
+import org.apache.doris.catalog.EnvFactory;
 import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.LoadException;
@@ -140,7 +141,8 @@ public class LoadLoadingTask extends LoadTask {
 
     private void executeOnce() throws Exception {
         // New one query id,
-        Coordinator curCoordinator = new Coordinator(callback.getCallbackId(), loadId, planner.getDescTable(),
+        Coordinator curCoordinator =  EnvFactory.getInstance().createCoordinator(callback.getCallbackId(),
+                loadId, planner.getDescTable(),
                 planner.getFragments(), planner.getScanNodes(), planner.getTimezone(), loadZeroTolerance);
         if (this.jobProfile != null) {
             this.jobProfile.setExecutionProfile(curCoordinator.getExecutionProfile());
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java
index 4095d1acf72..33429fcf25b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlProto.java
@@ -19,6 +19,7 @@ package org.apache.doris.mysql;
 
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.Env;
+import org.apache.doris.cloud.catalog.CloudEnv;
 import org.apache.doris.common.AuthenticationException;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
@@ -324,6 +325,22 @@ public class MysqlProto {
                 context.getState().setError(ErrorCode.ERR_BAD_DB_ERROR, "Only one dot can be in the name: " + db);
                 return false;
             }
+
+            // mysql -d
+            if (Config.isCloudMode()) {
+                try {
+                    dbName = ((CloudEnv) Env.getCurrentEnv()).analyzeCloudCluster(dbName, context);
+                } catch (DdlException e) {
+                    context.getState().setError(e.getMysqlErrorCode(), e.getMessage());
+                    sendResponsePacket(context);
+                    return false;
+                }
+
+                if (dbName == null || dbName.isEmpty()) {
+                    return true;
+                }
+            }
+
             String dbFullName = dbName;
 
             // check catalog and db exists
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java
index 2a64ddabe4c..1223778a67e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/AccessControllerManager.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.mysql.privilege;
 
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.TableName;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.AuthorizationInfo;
@@ -222,6 +223,17 @@ public class AccessControllerManager {
         return defaultAccessController.checkResourcePriv(currentUser, resourceName, wanted);
     }
 
+    // ==== Cloud ====
+    public boolean checkCloudPriv(ConnectContext ctx, String cloudName, PrivPredicate wanted, ResourceTypeEnum type) {
+        return checkCloudPriv(ctx.getCurrentUserIdentity(), cloudName, wanted, type);
+    }
+
+    public boolean checkCloudPriv(UserIdentity currentUser, String cloudName,
+                                  PrivPredicate wanted, ResourceTypeEnum type) {
+        return defaultAccessController.checkCloudPriv(currentUser, cloudName, wanted, type);
+    }
+
+
     public boolean checkWorkloadGroupPriv(ConnectContext ctx, String workloadGroupName, PrivPredicate wanted) {
         return checkWorkloadGroupPriv(ctx.getCurrentUserIdentity(), workloadGroupName, wanted);
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
index 60819af602a..f84e8bb4c90 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Auth.java
@@ -129,7 +129,7 @@ public class Auth implements Writable {
     }
 
     public enum PrivLevel {
-        GLOBAL, CATALOG, DATABASE, TABLE, RESOURCE, WORKLOAD_GROUP
+        GLOBAL, CATALOG, DATABASE, TABLE, RESOURCE, WORKLOAD_GROUP, CLUSTER
     }
 
     public Auth() {
@@ -400,7 +400,24 @@ public class Auth implements Writable {
     // ==== cloud ====
     public boolean checkCloudPriv(UserIdentity currentUser, String cloudName,
                                   PrivPredicate wanted, ResourceTypeEnum type) {
-        throw new RuntimeException("Auth.checkCloudPriv is not implemented");
+        readLock();
+        try {
+            ConnectContext ctx = ConnectContext.get();
+            if (ctx != null) {
+                if (ctx.getNoAuth()) {
+                    return true;
+                }
+            }
+            Set<String> roles = userRoleManager.getRolesByUser(currentUser);
+            for (String roleName : roles) {
+                if (roleManager.getRole(roleName).checkCloudPriv(cloudName, wanted, type)) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            readUnlock();
+        }
     }
 
     // ==== Other ====
@@ -1232,6 +1249,20 @@ public class Auth implements Writable {
             userAuthInfo.add(Joiner.on("; ").join(resourcePrivs));
         }
 
+        // cloudCluster
+        List<String> cloudClusterPrivs = Lists.newArrayList();
+        for (PrivEntry entry : getUserCloudClusterPrivTable(userIdent).entries) {
+            ResourcePrivEntry rEntry = (ResourcePrivEntry) entry;
+            PrivBitSet savedPrivs = rEntry.getPrivSet().copy();
+            cloudClusterPrivs.add(rEntry.getOrigResource() + ": " + savedPrivs.toString());
+        }
+
+        if (cloudClusterPrivs.isEmpty()) {
+            userAuthInfo.add(FeConstants.null_string);
+        } else {
+            userAuthInfo.add(Joiner.on("; ").join(cloudClusterPrivs));
+        }
+
         // workload group
         List<String> workloadGroupPrivs = Lists.newArrayList();
         for (PrivEntry entry : getUserWorkloadGroupPrivTable(userIdent).entries) {
@@ -1249,6 +1280,15 @@ public class Auth implements Writable {
         userAuthInfos.add(userAuthInfo);
     }
 
+    private ResourcePrivTable getUserCloudClusterPrivTable(UserIdentity userIdentity) {
+        ResourcePrivTable table = new ResourcePrivTable();
+        Set<String> roles = userRoleManager.getRolesByUser(userIdentity);
+        for (String roleName : roles) {
+            table.merge(roleManager.getRole(roleName).getCloudClusterPrivTable());
+        }
+        return table;
+    }
+
     private GlobalPrivTable getUserGlobalPrivTable(UserIdentity userIdentity) {
         GlobalPrivTable table = new GlobalPrivTable();
         Set<Role> roles = getRolesByUserWithLdap(userIdentity);
@@ -1702,7 +1742,7 @@ public class Auth implements Writable {
             //grant global auth
             if (globalPrivEntry.privSet.containsResourcePriv()) {
                 roleManager.addOrMergeRole(new Role(roleManager.getUserDefaultRoleName(user.getUserIdentity()),
-                        ResourcePattern.ALL, PrivBitSet.of(Privilege.USAGE_PRIV)), false);
+                        ResourcePattern.ALL_GENERAL, PrivBitSet.of(Privilege.USAGE_PRIV)), false);
             }
             PrivBitSet copy = globalPrivEntry.privSet.copy();
             copy.unset(Privilege.USAGE_PRIV.getIdx());
@@ -1758,7 +1798,7 @@ public class Auth implements Writable {
         for (PrivEntry privEntry : resourcePrivTableEntries) {
             ResourcePrivEntry resourcePrivEntry = (ResourcePrivEntry) privEntry;
             ResourcePattern resourcePattern = new ResourcePattern(
-                    ClusterNamespace.getNameFromFullName(resourcePrivEntry.origResource));
+                    ClusterNamespace.getNameFromFullName(resourcePrivEntry.origResource), ResourceTypeEnum.GENERAL);
             resourcePattern.analyze();
             Role newRole = new Role(roleManager.getUserDefaultRoleName(resourcePrivEntry.userIdentity),
                     resourcePattern, resourcePrivEntry.privSet);
@@ -1777,4 +1817,32 @@ public class Auth implements Writable {
         sb.append(ldapInfo).append("\n");
         return sb.toString();
     }
+
+    // ====== CLOUD ======
+    public List<String> getCloudClusterUsers(String clusterName) {
+        return propertyMgr.getCloudClusterUsers(userManager.getAllUsers(), clusterName);
+    }
+
+    public Set<String> getAllUsers() {
+        return userManager.getAllUsers();
+    }
+
+    // just for ut
+    public UserManager getUserManager() {
+        return userManager;
+    }
+
+    public String getDefaultCloudCluster(String user) {
+        String cluster = "";
+        readLock();
+        try {
+            cluster = propertyMgr.getDefaultCloudCluster(user);
+        } catch (DdlException e) {
+            LOG.warn("cant get default cloud cluster , user {}", user);
+        } finally {
+            readUnlock();
+        }
+        return cluster;
+    }
+    // ====== CLOUD ======
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java
index de39252f95b..f9d4cdbd03c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/CatalogAccessController.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.mysql.privilege;
 
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.common.AuthorizationException;
 
@@ -73,4 +74,7 @@ public interface CatalogAccessController {
 
     void checkColsPriv(UserIdentity currentUser, String ctl, String db, String tbl,
             Set<String> cols, PrivPredicate wanted) throws AuthorizationException;
+
+    // ==== Cloud ====
+    boolean checkCloudPriv(UserIdentity currentUser, String resourceName, PrivPredicate wanted, ResourceTypeEnum type);
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/InternalAccessController.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/InternalAccessController.java
index 8faf58a56b9..0e3fac62134 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/InternalAccessController.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/InternalAccessController.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.mysql.privilege;
 
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.common.AuthorizationException;
 
@@ -64,4 +65,10 @@ public class InternalAccessController implements CatalogAccessController {
     public boolean checkWorkloadGroupPriv(UserIdentity currentUser, String workloadGroupName, PrivPredicate wanted) {
         return auth.checkWorkloadGroupPriv(currentUser, workloadGroupName, wanted);
     }
+
+    @Override
+    public boolean checkCloudPriv(UserIdentity currentUser, String resourceName,
+                                  PrivPredicate wanted, ResourceTypeEnum type) {
+        return auth.checkResourcePriv(currentUser, resourceName, wanted);
+    }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java
index 58ad92fbadb..0193972430c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivBitSet.java
@@ -18,6 +18,7 @@
 package org.apache.doris.mysql.privilege;
 
 import org.apache.doris.analysis.CompoundPredicate.Operator;
+import org.apache.doris.analysis.ResourcePattern;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.common.FeMetaVersion;
 import org.apache.doris.common.io.Text;
@@ -32,7 +33,9 @@ import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 // ....0000000000
 //        ^     ^
@@ -48,17 +51,17 @@ public class PrivBitSet implements Writable {
     }
 
     public void set(int index) {
-        Preconditions.checkState(index < Privilege.privileges.length, index);
+        Preconditions.checkState(Privilege.privileges.containsKey(index), index);
         set |= 1 << index;
     }
 
     public void unset(int index) {
-        Preconditions.checkState(index < Privilege.privileges.length, index);
+        Preconditions.checkState(Privilege.privileges.containsKey(index), index);
         set &= ~(1 << index);
     }
 
     public boolean get(int index) {
-        Preconditions.checkState(index < Privilege.privileges.length, index);
+        Preconditions.checkState(Privilege.privileges.containsKey(index), index);
         return (set & (1 << index)) > 0;
     }
 
@@ -101,7 +104,7 @@ public class PrivBitSet implements Writable {
     }
 
     public boolean containsResourcePriv() {
-        return containsPrivs(Privilege.USAGE_PRIV);
+        return containsPrivs(Privilege.USAGE_PRIV, Privilege.CLUSTER_USAGE_PRIV);
     }
 
     public boolean containsDbTablePriv() {
@@ -120,11 +123,11 @@ public class PrivBitSet implements Writable {
 
     public List<Privilege> toPrivilegeList() {
         List<Privilege> privs = Lists.newArrayList();
-        for (int i = 0; i < Privilege.privileges.length; i++) {
-            if (get(i)) {
-                privs.add(Privilege.getPriv(i));
+        Privilege.privileges.keySet().forEach(idx -> {
+            if (get(idx)) {
+                privs.add(Privilege.getPriv(idx));
             }
-        }
+        });
         return privs;
     }
 
@@ -153,11 +156,11 @@ public class PrivBitSet implements Writable {
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < Privilege.privileges.length; i++) {
-            if (get(i)) {
-                sb.append(Privilege.getPriv(i)).append(" ");
+        Privilege.privileges.keySet().forEach(idx -> {
+            if (get(idx)) {
+                sb.append(Privilege.getPriv(idx)).append(" ");
             }
-        }
+        });
         return sb.toString();
     }
 
@@ -175,4 +178,20 @@ public class PrivBitSet implements Writable {
     public void write(DataOutput out) throws IOException {
         Text.writeString(out, GsonUtils.GSON.toJson(this));
     }
+
+    public static Set<Privilege> convertResourcePrivToCloudPriv(ResourcePattern resourcePattern,
+                                                                Set<Privilege> privileges) {
+        PrivBitSet privs = PrivBitSet.of();
+        for (Privilege privilege : privileges) {
+            if (resourcePattern.isGeneralResource()) {
+                privs.or(PrivBitSet.of(privilege));
+                continue;
+            }
+
+            if (resourcePattern.isClusterResource()) {
+                privs.or(PrivBitSet.of(Privilege.CLUSTER_USAGE_PRIV));
+            }
+        }
+        return new HashSet<>(privs.toPrivilegeList());
+    }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java
index 191d27cbdd2..7ca417d867f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/PrivPredicate.java
@@ -98,8 +98,8 @@ public class PrivPredicate {
 
     // resource/workloadGroup usage
     public static final PrivPredicate USAGE = PrivPredicate.of(PrivBitSet.of(Privilege.ADMIN_PRIV,
-            Privilege.USAGE_PRIV),
-            Operator.OR);
+            Privilege.USAGE_PRIV, Privilege.CLUSTER_USAGE_PRIV), Operator.OR);
+
 
     // all
     public static final PrivPredicate ALL = PrivPredicate.of(PrivBitSet.of(Privilege.NODE_PRIV,
@@ -109,7 +109,8 @@ public class PrivPredicate {
             Privilege.ALTER_PRIV,
             Privilege.CREATE_PRIV,
             Privilege.DROP_PRIV,
-            Privilege.USAGE_PRIV),
+            Privilege.USAGE_PRIV,
+            Privilege.CLUSTER_USAGE_PRIV),
             Operator.OR);
 
     private PrivBitSet privs;
@@ -120,7 +121,7 @@ public class PrivPredicate {
         this.op = op;
     }
 
-    private static PrivPredicate of(PrivBitSet privs, Operator op) {
+    public static PrivPredicate of(PrivBitSet privs, Operator op) {
         final PrivPredicate predicate = new PrivPredicate(privs, op);
         return predicate;
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Privilege.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Privilege.java
index 54130a5d450..4cb5d49efa2 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Privilege.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Privilege.java
@@ -17,8 +17,11 @@
 
 package org.apache.doris.mysql.privilege;
 
+import org.apache.doris.common.Config;
+
 import com.google.common.collect.ImmutableMap;
 
+import java.util.HashMap;
 import java.util.Map;
 
 public enum Privilege {
@@ -31,23 +34,47 @@ public enum Privilege {
     CREATE_PRIV("Create_priv", 6, "Privilege for creating database or table"),
     DROP_PRIV("Drop_priv", 7, "Privilege for dropping database or table"),
     USAGE_PRIV("Usage_priv", 8, "Privilege for using resource or workloadGroup"),
-    SHOW_VIEW_PRIV("Show_view_priv", 9, "Privilege for show create view");
+    // in doris code < VERSION_130
+    SHOW_VIEW_PRIV_DEPRECATED("show_view_priv", 9, "Privilege for show create view"),
+
+    // in cloud code < VERSION_130
+    CLUSTER_USAGE_PRIV_DEPRECATED("Cluster_Usage_priv", 9, "Privilege for using cluster"),
+    STAGE_USAGE_PRIV_DEPRECATED("Stage_Usage_priv", 10, "Privilege for using stage"),
+    SHOW_VIEW_PRIV_CLOUD_DEPRECATED("Show_view_priv", 11, "Privilege for show create view"),
+    // compatible doris and cloud, and 9 ~ 11 has been contaminated
+    CLUSTER_USAGE_PRIV("Cluster_Usage_priv", 12, "Privilege for using cluster"),
+    // 13 placeholder for stage
+    STAGE_USAGE_PRIV("Stage_Usage_priv", 13, "Privilege for using stage"),
+    SHOW_VIEW_PRIV("Show_view_priv", 14, "Privilege for show create view");
+
+    public static final Map<Integer, Privilege> privileges;
+
+    static {
+        privileges = new HashMap<>();
+        privileges.put(0, NODE_PRIV);
+        privileges.put(1, ADMIN_PRIV);
+        privileges.put(2, GRANT_PRIV);
+        privileges.put(3, SELECT_PRIV);
+        privileges.put(4, LOAD_PRIV);
+        privileges.put(5, ALTER_PRIV);
+        privileges.put(6, CREATE_PRIV);
+        privileges.put(7, DROP_PRIV);
+        privileges.put(8, USAGE_PRIV);
+        if (Config.isCloudMode()) {
+            privileges.put(9, CLUSTER_USAGE_PRIV_DEPRECATED);
+            privileges.put(10, STAGE_USAGE_PRIV_DEPRECATED);
+            privileges.put(11, SHOW_VIEW_PRIV_CLOUD_DEPRECATED);
+        } else {
+            privileges.put(9, SHOW_VIEW_PRIV_DEPRECATED);
+        }
+        privileges.put(12, CLUSTER_USAGE_PRIV);
+        privileges.put(13, STAGE_USAGE_PRIV);
+        privileges.put(14, SHOW_VIEW_PRIV);
+    }
 
-    public static Privilege[] privileges = {
-            NODE_PRIV,
-            ADMIN_PRIV,
-            GRANT_PRIV,
-            SELECT_PRIV,
-            LOAD_PRIV,
-            ALTER_PRIV,
-            CREATE_PRIV,
-            DROP_PRIV,
-            USAGE_PRIV,
-            SHOW_VIEW_PRIV
-    };
 
     // only GRANT_PRIV and USAGE_PRIV can grant on resource
-    public static Privilege[] notBelongToResourcePrivileges = {
+    public static final Privilege[] notBelongToResourcePrivileges = {
             NODE_PRIV,
             ADMIN_PRIV,
             SELECT_PRIV,
@@ -55,11 +82,11 @@ public enum Privilege {
             ALTER_PRIV,
             CREATE_PRIV,
             DROP_PRIV,
-            SHOW_VIEW_PRIV
+            SHOW_VIEW_PRIV,
     };
 
     // only GRANT_PRIV and USAGE_PRIV can grant on workloadGroup
-    public static Privilege[] notBelongToWorkloadGroupPrivileges = {
+    public static final Privilege[] notBelongToWorkloadGroupPrivileges = {
             NODE_PRIV,
             ADMIN_PRIV,
             SELECT_PRIV,
@@ -67,10 +94,10 @@ public enum Privilege {
             ALTER_PRIV,
             CREATE_PRIV,
             DROP_PRIV,
-            SHOW_VIEW_PRIV
+            SHOW_VIEW_PRIV,
     };
 
-    public static Map<Privilege, String> privInDorisToMysql =
+    public static final Map<Privilege, String> privInDorisToMysql =
             ImmutableMap.<Privilege, String>builder() // No NODE_PRIV and ADMIN_PRIV in the mysql
                     .put(SELECT_PRIV, "SELECT")
                     .put(LOAD_PRIV, "INSERT")
@@ -78,6 +105,7 @@ public enum Privilege {
                     .put(CREATE_PRIV, "CREATE")
                     .put(DROP_PRIV, "DROP")
                     .put(USAGE_PRIV, "USAGE")
+                    .put(CLUSTER_USAGE_PRIV, "USAGE")
                     .put(SHOW_VIEW_PRIV, "SHOW VIEW")
                     .build();
 
@@ -104,10 +132,10 @@ public enum Privilege {
     }
 
     public static Privilege getPriv(int index) {
-        if (index < 0 || index > Privilege.values().length - 1) {
+        if (!privileges.containsKey(index)) {
             return null;
         }
-        return privileges[index];
+        return privileges.get(index);
     }
 
     public static boolean satisfy(PrivBitSet grantPriv, PrivPredicate wanted) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java
index 59a2316dadc..91b2eae3731 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/Role.java
@@ -18,12 +18,14 @@
 package org.apache.doris.mysql.privilege;
 
 import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.analysis.WorkloadGroupPattern;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.cluster.ClusterNamespace;
 import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
 import org.apache.doris.common.FeMetaVersion;
 import org.apache.doris.common.PatternMatcherException;
@@ -83,6 +85,8 @@ public class Role implements Writable, GsonPostProcessable {
     private Map<TablePattern, PrivBitSet> tblPatternToPrivs = Maps.newConcurrentMap();
     @SerializedName(value = "resourcePatternToPrivs")
     private Map<ResourcePattern, PrivBitSet> resourcePatternToPrivs = Maps.newConcurrentMap();
+    @SerializedName(value = "clusterPatternToPrivs")
+    private Map<ResourcePattern, PrivBitSet> clusterPatternToPrivs = Maps.newConcurrentMap();
     @SerializedName(value = "workloadGroupPatternToPrivs")
     private Map<WorkloadGroupPattern, PrivBitSet> workloadGroupPatternToPrivs = Maps.newConcurrentMap();
     @SerializedName(value = "colPrivMap")
@@ -94,6 +98,8 @@ public class Role implements Writable, GsonPostProcessable {
     private DbPrivTable dbPrivTable = new DbPrivTable();
     private TablePrivTable tablePrivTable = new TablePrivTable();
     private ResourcePrivTable resourcePrivTable = new ResourcePrivTable();
+    // reuse ResourcePrivTable and ResourcePrivEntry for grant/revoke cloudClusterName
+    private ResourcePrivTable cloudClusterPrivTable = new ResourcePrivTable();
     private WorkloadGroupPrivTable workloadGroupPrivTable = new WorkloadGroupPrivTable();
 
 
@@ -129,7 +135,12 @@ public class Role implements Writable, GsonPostProcessable {
 
     public Role(String roleName, ResourcePattern resourcePattern, PrivBitSet privs) throws DdlException {
         this.roleName = roleName;
-        this.resourcePatternToPrivs.put(resourcePattern, privs);
+        // grant has trans privs
+        if (resourcePattern.isGeneralResource()) {
+            this.resourcePatternToPrivs.put(resourcePattern, privs);
+        } else if (resourcePattern.isClusterResource()) {
+            this.clusterPatternToPrivs.put(resourcePattern, privs);
+        }
         grantPrivs(resourcePattern, privs.copy());
     }
 
@@ -175,6 +186,10 @@ public class Role implements Writable, GsonPostProcessable {
         return resourcePatternToPrivs;
     }
 
+    public Map<ResourcePattern, PrivBitSet> getClusterPatternToPrivs() {
+        return clusterPatternToPrivs;
+    }
+
     public Map<WorkloadGroupPattern, PrivBitSet> getWorkloadGroupPatternToPrivs() {
         return workloadGroupPatternToPrivs;
     }
@@ -199,6 +214,15 @@ public class Role implements Writable, GsonPostProcessable {
             }
             grantPrivs(entry.getKey(), entry.getValue().copy());
         }
+        for (Map.Entry<ResourcePattern, PrivBitSet> entry : other.clusterPatternToPrivs.entrySet()) {
+            if (clusterPatternToPrivs.containsKey(entry.getKey())) {
+                PrivBitSet existPrivs = clusterPatternToPrivs.get(entry.getKey());
+                existPrivs.or(entry.getValue());
+            } else {
+                clusterPatternToPrivs.put(entry.getKey(), entry.getValue());
+            }
+            grantPrivs(entry.getKey(), entry.getValue().copy());
+        }
         for (Map.Entry<WorkloadGroupPattern, PrivBitSet> entry : other.workloadGroupPatternToPrivs.entrySet()) {
             if (workloadGroupPatternToPrivs.containsKey(entry.getKey())) {
                 PrivBitSet existPrivs = workloadGroupPatternToPrivs.get(entry.getKey());
@@ -370,6 +394,24 @@ public class Role implements Writable, GsonPostProcessable {
         return false;
     }
 
+    public boolean checkCloudPriv(String cloudName,
+                                  PrivPredicate wanted, ResourceTypeEnum type) {
+        ResourcePrivTable cloudPrivTable = getCloudPrivTable(type);
+        if (cloudPrivTable == null) {
+            LOG.warn("cloud resource type err: {}", type);
+            return false;
+        }
+
+        PrivBitSet savedPrivs = PrivBitSet.of();
+        if (checkGlobalInternal(wanted, savedPrivs)
+                || checkCloudInternal(cloudName, wanted, savedPrivs, cloudPrivTable, type)) {
+            return true;
+        }
+
+        LOG.debug("failed to get wanted privs: {}, granted: {}", wanted, savedPrivs);
+        return false;
+    }
+
     public boolean checkColPriv(String ctl, String db, String tbl, String col, PrivPredicate wanted) {
         Optional<Privilege> colPrivilege = wanted.getColPrivilege();
         Preconditions.checkState(colPrivilege.isPresent(), "this privPredicate should not use checkColPriv:" + wanted);
@@ -408,6 +450,13 @@ public class Role implements Writable, GsonPostProcessable {
         return Privilege.satisfy(savedPrivs, wanted);
     }
 
+    private boolean checkCloudInternal(String cloudName, PrivPredicate wanted,
+                                       PrivBitSet savedPrivs, ResourcePrivTable table, ResourceTypeEnum type) {
+        table.getPrivs(cloudName, savedPrivs);
+        return Privilege.satisfy(savedPrivs, wanted);
+    }
+
+
     public boolean checkWorkloadGroupPriv(String workloadGroupName, PrivPredicate wanted) {
         // For compatibility with older versions, it is not needed to check the privileges of the default group.
         if (WorkloadGroupMgr.DEFAULT_GROUP_NAME.equals(workloadGroupName)) {
@@ -480,6 +529,10 @@ public class Role implements Writable, GsonPostProcessable {
         return resourcePrivTable;
     }
 
+    public ResourcePrivTable getCloudClusterPrivTable() {
+        return cloudClusterPrivTable;
+    }
+
     public WorkloadGroupPrivTable getWorkloadGroupPrivTable() {
         return workloadGroupPrivTable;
     }
@@ -510,7 +563,11 @@ public class Role implements Writable, GsonPostProcessable {
                 grantGlobalPrivs(privs);
                 break;
             case RESOURCE:
-                grantResourcePrivs(resourcePattern.getResourceName(), privs);
+                if (resourcePattern.isClusterResource()) {
+                    grantCloudClusterPrivs(resourcePattern.getResourceName(), false, false, privs);
+                } else {
+                    grantResourcePrivs(resourcePattern.getResourceName(), privs);
+                }
                 break;
             default:
                 Preconditions.checkNotNull(null, resourcePattern.getPrivLevel());
@@ -586,6 +643,27 @@ public class Role implements Writable, GsonPostProcessable {
         resourcePrivTable.addEntry(entry, false, false);
     }
 
+    private void grantCloudClusterPrivs(String cloudClusterName, boolean errOnExist,
+                                        boolean errOnNonExist, PrivBitSet privs) throws DdlException {
+        ResourcePrivEntry entry;
+        try {
+            entry = ResourcePrivEntry.create(cloudClusterName, privs);
+        } catch (AnalysisException | PatternMatcherException e) {
+            throw new DdlException(e.getMessage());
+        }
+        cloudClusterPrivTable.addEntry(entry, errOnExist, errOnNonExist);
+        LOG.info("cloud cluster add list {}", cloudClusterPrivTable);
+    }
+
+    private ResourcePrivTable getCloudPrivTable(ResourceTypeEnum type) {
+        if (type == ResourceTypeEnum.CLUSTER) {
+            return cloudClusterPrivTable;
+        } else {
+            // cloud resource not GENERAL type
+            return null;
+        }
+    }
+
     private void grantCatalogPrivs(String ctl, PrivBitSet privs) throws DdlException {
         CatalogPrivEntry entry;
         try {
@@ -627,6 +705,17 @@ public class Role implements Writable, GsonPostProcessable {
         workloadGroupPrivTable.addEntry(entry, false, false);
     }
 
+    private void revokeCloudClusterPrivs(String cloudClusterName, PrivBitSet privs) throws DdlException {
+        ResourcePrivEntry entry;
+        try {
+            entry = ResourcePrivEntry.create(cloudClusterName, privs);
+        } catch (AnalysisException | PatternMatcherException e) {
+            throw new DdlException(e.getMessage());
+        }
+        cloudClusterPrivTable.revoke(entry, false, true);
+        LOG.info("cloud cluster revoke list {}", cloudClusterPrivTable);
+    }
+
     public void revokePrivs(TablePattern tblPattern, PrivBitSet privs, Map<ColPrivilegeKey, Set<String>> colPrivileges,
             boolean errOnNonExist)
             throws DdlException {
@@ -661,7 +750,12 @@ public class Role implements Writable, GsonPostProcessable {
 
     public void revokePrivs(ResourcePattern resourcePattern, PrivBitSet privs, boolean errOnNonExist)
             throws DdlException {
-        PrivBitSet existingPriv = resourcePatternToPrivs.get(resourcePattern);
+        PrivBitSet existingPriv = null;
+        if (resourcePattern.isClusterResource()) {
+            existingPriv = clusterPatternToPrivs.get(resourcePattern);
+        } else {
+            existingPriv = resourcePatternToPrivs.get(resourcePattern);
+        }
         if (existingPriv == null) {
             if (errOnNonExist) {
                 throw new DdlException(resourcePattern + " does not exist in role " + roleName);
@@ -678,7 +772,11 @@ public class Role implements Writable, GsonPostProcessable {
                 revokeGlobalPrivs(privs);
                 break;
             case RESOURCE:
-                revokeResourcePrivs(resourcePattern.getResourceName(), privs);
+                if (resourcePattern.isGeneralResource()) {
+                    revokeResourcePrivs(resourcePattern.getResourceName(), privs);
+                } else {
+                    revokeCloudClusterPrivs(resourcePattern.getResourceName(), privs);
+                }
                 break;
         }
     }
@@ -822,7 +920,6 @@ public class Role implements Writable, GsonPostProcessable {
                 privBitSet.or(modifiedGlobalPrivs);
             }
         }
-
         // rebuild these priv tables
         rebuildPrivTables();
     }
@@ -832,6 +929,7 @@ public class Role implements Writable, GsonPostProcessable {
         StringBuilder sb = new StringBuilder();
         sb.append("role: ").append(roleName).append(", db table privs: ").append(tblPatternToPrivs);
         sb.append(", resource privs: ").append(resourcePatternToPrivs);
+        sb.append(", cluster privs: ").append(clusterPatternToPrivs);
         sb.append(", workload group privs: ").append(workloadGroupPatternToPrivs);
         return sb.toString();
     }
@@ -890,9 +988,49 @@ public class Role implements Writable, GsonPostProcessable {
     @Override
     public void gsonPostProcess() {
         removeClusterPrefix();
+        compatibilityErrEnum();
         rebuildPrivTables();
     }
 
+    private void compatibilityErrEnum() {
+        if (Env.getCurrentEnvJournalVersion() >= FeMetaVersion.VERSION_129) {
+            if (Config.isNotCloudMode()) {
+                // not cloud mode,
+                // SHOW_VIEW_PRIV_DEPRECATED -> SHOW_VIEW_PRIV (9 -> 14)
+                tblPatternToPrivs.values().forEach(privBitSet -> {
+                    if (privBitSet.containsPrivs(Privilege.SHOW_VIEW_PRIV_DEPRECATED)) {
+                        // remove SHOW_VIEW_PRIV_DEPRECATED
+                        privBitSet.unset(Privilege.SHOW_VIEW_PRIV_DEPRECATED.getIdx());
+                        // add SHOW_VIEW_PRIV
+                        privBitSet.set(Privilege.SHOW_VIEW_PRIV.getIdx());
+                    }
+                });
+            } else {
+                // cloud mode
+                // CLUSTER_USAGE_PRIV_DEPRECATED -> CLUSTER_USAGE_PRIV (9 -> 12)
+                clusterPatternToPrivs.values().forEach(privBitSet -> {
+                    if (privBitSet.containsPrivs(Privilege.CLUSTER_USAGE_PRIV_DEPRECATED)) {
+                        // remove CLUSTER_USAGE_PRIV_DEPRECATED
+                        privBitSet.unset(Privilege.CLUSTER_USAGE_PRIV_DEPRECATED.getIdx());
+                        // add CLUSTER_USAGE_PRIV
+                        privBitSet.set(Privilege.CLUSTER_USAGE_PRIV.getIdx());
+                    }
+                });
+                // todo(dx): if doris has stage
+                // STAGE_USAGE_PRIV_DEPRECATED -> STAGE_USAGE_PRIV (10 -> 13)
+                // SHOW_VIEW_PRIV_CLOUD_DEPRECATED -> SHOW_VIEW_PRIV (11 -> 14)
+                tblPatternToPrivs.values().forEach(privBitSet -> {
+                    if (privBitSet.containsPrivs(Privilege.SHOW_VIEW_PRIV_CLOUD_DEPRECATED)) {
+                        // remove SHOW_VIEW_PRIV_CLOUD_DEPRECATED
+                        privBitSet.unset(Privilege.SHOW_VIEW_PRIV_CLOUD_DEPRECATED.getIdx());
+                        // add SHOW_VIEW_PRIV
+                        privBitSet.set(Privilege.SHOW_VIEW_PRIV.getIdx());
+                    }
+                });
+            }
+        }
+    }
+
     private void rebuildPrivTables() {
         globalPrivTable = new GlobalPrivTable();
         catalogPrivTable = new CatalogPrivTable();
@@ -914,6 +1052,17 @@ public class Role implements Writable, GsonPostProcessable {
                 LOG.warn("grant failed,", e);
             }
         }
+        for (Entry<ResourcePattern, PrivBitSet> entry : clusterPatternToPrivs.entrySet()) {
+            try {
+                grantPrivs(entry.getKey(), entry.getValue().copy());
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("cloud cluster entry {}", entry);
+                }
+            } catch (DdlException e) {
+                LOG.warn("grant failed exception {} entry {}", e, entry);
+            }
+        }
+        // todo(dx): if add stage priv
         for (Entry<WorkloadGroupPattern, PrivBitSet> entry : workloadGroupPatternToPrivs.entrySet()) {
             try {
                 grantPrivs(entry.getKey(), entry.getValue().copy());
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java
index 7f48ed87cfa..519743a5fdf 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/RoleManager.java
@@ -100,6 +100,14 @@ public class RoleManager implements Writable, GsonPostProcessable {
         roles.remove(qualifiedRole);
     }
 
+    private void replaceResourceLevel(Map<PrivLevel, List<Entry<ResourcePattern, PrivBitSet>>> map, PrivLevel type) {
+        List<Entry<ResourcePattern, PrivBitSet>> clusterSet = map.get(PrivLevel.RESOURCE);
+        if (clusterSet != null && !clusterSet.isEmpty()) {
+            map.remove(PrivLevel.RESOURCE);
+            map.put(type, clusterSet);
+        }
+    }
+
     public Role revokePrivs(String name, TablePattern tblPattern, PrivBitSet privs,
             Map<ColPrivilegeKey, Set<String>> colPrivileges, boolean errOnNonExist)
             throws DdlException {
@@ -151,7 +159,13 @@ public class RoleManager implements Writable, GsonPostProcessable {
             List<String> info = Lists.newArrayList();
             info.add(role.getRoleName());
             info.add(Joiner.on(", ").join(Env.getCurrentEnv().getAuth().getRoleUsers(role.getRoleName())));
+
+            Map<PrivLevel, List<Entry<ResourcePattern, PrivBitSet>>> clusterMap = role.getClusterPatternToPrivs()
+                    .entrySet().stream().collect(Collectors.groupingBy(entry -> entry.getKey().getPrivLevel()));
+            replaceResourceLevel(clusterMap, PrivLevel.CLUSTER);
+
             Map<PrivLevel, String> infoMap =
+                    Stream.concat(
                     Stream.concat(
                             role.getTblPatternToPrivs().entrySet().stream()
                                     .collect(Collectors.groupingBy(entry -> entry.getKey().getPrivLevel())).entrySet()
@@ -162,6 +176,8 @@ public class RoleManager implements Writable, GsonPostProcessable {
                                     role.getWorkloadGroupPatternToPrivs().entrySet().stream()
                                             .collect(Collectors.groupingBy(entry -> entry.getKey().getPrivLevel()))
                                             .entrySet().stream())
+                    ),
+                    clusterMap.entrySet().stream()
                     ).collect(Collectors.toMap(Entry::getKey, entry -> {
                                 if (entry.getKey() == PrivLevel.GLOBAL) {
                                     return entry.getValue().stream().findFirst().map(priv -> priv.getValue().toString())
@@ -173,7 +189,8 @@ public class RoleManager implements Writable, GsonPostProcessable {
                                 }
                             }, (s1, s2) -> s1 + " " + s2
                     ));
-            Stream.of(PrivLevel.GLOBAL, PrivLevel.CATALOG, PrivLevel.DATABASE, PrivLevel.TABLE, PrivLevel.RESOURCE)
+            Stream.of(PrivLevel.GLOBAL, PrivLevel.CATALOG, PrivLevel.DATABASE, PrivLevel.TABLE, PrivLevel.RESOURCE,
+                        PrivLevel.CLUSTER)
                     .forEach(level -> {
                         String infoItem = infoMap.get(level);
                         if (Strings.isNullOrEmpty(infoItem)) {
@@ -260,8 +277,7 @@ public class RoleManager implements Writable, GsonPostProcessable {
             return roleManager;
         } else {
             String json = Text.readString(in);
-            RoleManager rm = GsonUtils.GSON.fromJson(json, RoleManager.class);
-            return rm;
+            return GsonUtils.GSON.fromJson(json, RoleManager.class);
         }
     }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/User.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/User.java
index afac0767e07..781e2240413 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/User.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/User.java
@@ -48,6 +48,9 @@ public class User implements Comparable<User>, Writable, GsonPostProcessable {
     @SerializedName(value = "password")
     private Password password;
 
+    @SerializedName(value = "userid")
+    private String origUserId = "";
+
     public User() {
     }
 
@@ -64,6 +67,16 @@ public class User implements Comparable<User>, Writable, GsonPostProcessable {
         }
     }
 
+    // ====== CLOUD ======
+    public String getUserId() {
+        return origUserId;
+    }
+
+    public void setUserId(String userId) {
+        this.origUserId = userId;
+    }
+    // ====== CLOUD ======
+
 
     public Password getPassword() {
         return password;
@@ -135,7 +148,8 @@ public class User implements Comparable<User>, Writable, GsonPostProcessable {
     public String toString() {
         StringBuilder sb = new StringBuilder();
         sb.append("userIdentity: ").append(userIdentity).append(", isSetByDomainResolver: ")
-                .append(isSetByDomainResolver).append(", domainUserIdentity: ").append(domainUserIdentity);
+                .append(isSetByDomainResolver).append(", domainUserIdentity: ").append(domainUserIdentity)
+            .append(", userId: ").append(origUserId);
         return sb.toString();
     }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java
index e1c0faa1677..3f2dbe17ca4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserManager.java
@@ -39,6 +39,7 @@ import com.google.gson.annotations.SerializedName;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.util.Strings;
 
 import java.io.DataInput;
 import java.io.DataOutput;
@@ -334,4 +335,26 @@ public class UserManager implements Writable, GsonPostProcessable {
     public void gsonPostProcess() throws IOException {
         removeClusterPrefix();
     }
+
+    // ====== CLOUD ======
+    public Set<String> getAllUsers() {
+        return nameToUsers.keySet();
+    }
+
+    public String getUserId(String userName) {
+        if (!nameToUsers.containsKey(userName)) {
+            LOG.warn("can't find userName {} 's userId, nameToUsers {}", userName, nameToUsers);
+            return "";
+        }
+        List<User> users = nameToUsers.get(userName);
+        if (users.isEmpty()) {
+            LOG.warn("userName {}  empty users in map {}", userName, nameToUsers);
+        }
+        // here, all the users has same userid, just return one
+        String userId = users.stream().map(User::getUserId).filter(Strings::isNotEmpty).findFirst().orElse("");
+        LOG.debug("userName {}, userId {}, map {}", userName, userId, nameToUsers);
+        return userId;
+    }
+
+    // ====== CLOUD =====
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserProperty.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserProperty.java
index 2375db0d920..dd56bcf235d 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserProperty.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserProperty.java
@@ -29,6 +29,7 @@ import org.apache.doris.common.UserException;
 import org.apache.doris.common.io.Text;
 import org.apache.doris.common.io.Writable;
 import org.apache.doris.load.DppConfig;
+import org.apache.doris.persist.gson.GsonUtils;
 import org.apache.doris.resource.Tag;
 
 import com.google.common.base.Joiner;
@@ -36,6 +37,7 @@ import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import com.google.gson.annotations.SerializedName;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -80,19 +82,29 @@ public class UserProperty implements Writable {
 
     private static final String PROP_WORKLOAD_GROUP = "default_workload_group";
 
+    public static final String DEFAULT_CLOUD_CLUSTER = "default_cloud_cluster";
+
     // for system user
     public static final Set<Pattern> ADVANCED_PROPERTIES = Sets.newHashSet();
     // for normal user
     public static final Set<Pattern> COMMON_PROPERTIES = Sets.newHashSet();
 
+    @SerializedName(value = "qualifiedUser")
     private String qualifiedUser;
 
+    @SerializedName(value = "commonProperties")
     private CommonUserProperties commonProperties = new CommonUserProperties();
 
     // load cluster
+    @SerializedName(value = "defaultLoadCluster")
     private String defaultLoadCluster = null;
+
+    @SerializedName(value = "clusterToDppConfig")
     private Map<String, DppConfig> clusterToDppConfig = Maps.newHashMap();
 
+    @SerializedName(value = "defaultCloudCluster")
+    private String defaultCloudCluster = null;
+
     /*
      *  We keep white list here to save Baidu domain name (BNS) or DNS as white list.
      *  Each frontend will periodically resolve the domain name to ip, and update the privilege table.
@@ -128,6 +140,7 @@ public class UserProperty implements Writable {
         COMMON_PROPERTIES.add(Pattern.compile("^" + PROP_LOAD_CLUSTER + "." + DppConfig.CLUSTER_NAME_REGEX + ".",
                 Pattern.CASE_INSENSITIVE));
         COMMON_PROPERTIES.add(Pattern.compile("^" + PROP_WORKLOAD_GROUP + "$", Pattern.CASE_INSENSITIVE));
+        COMMON_PROPERTIES.add(Pattern.compile("^" + DEFAULT_CLOUD_CLUSTER + "$", Pattern.CASE_INSENSITIVE));
     }
 
     public UserProperty() {
@@ -204,6 +217,7 @@ public class UserProperty implements Writable {
         String workloadGroup = this.commonProperties.getWorkloadGroup();
 
         String newDefaultLoadCluster = defaultLoadCluster;
+        String newDefaultCloudCluster = defaultCloudCluster;
         Map<String, DppConfig> newDppConfigs = Maps.newHashMap(clusterToDppConfig);
 
         // update
@@ -239,6 +253,15 @@ public class UserProperty implements Writable {
                 }
 
                 newDefaultLoadCluster = value;
+            }  else if (keyArr[0].equalsIgnoreCase(DEFAULT_CLOUD_CLUSTER)) {
+                // set property "DEFAULT_CLOUD_CLUSTER" = "cluster1"
+                if (keyArr.length != 1) {
+                    throw new DdlException(DEFAULT_CLOUD_CLUSTER + " format error");
+                }
+                if (value == null) {
+                    value = "";
+                }
+                newDefaultCloudCluster = value;
             } else if (keyArr[0].equalsIgnoreCase(PROP_MAX_QUERY_INSTANCES)) {
                 // set property "max_query_instances" = "1000"
                 if (keyArr.length != 1) {
@@ -370,6 +393,7 @@ public class UserProperty implements Writable {
             defaultLoadCluster = null;
         }
         clusterToDppConfig = newDppConfigs;
+        defaultCloudCluster = newDefaultCloudCluster;
     }
 
     private long getLongProperty(String key, String value, String[] keyArr, String propName) throws DdlException {
@@ -444,6 +468,10 @@ public class UserProperty implements Writable {
         }
     }
 
+    public String getDefaultCloudCluster() {
+        return defaultCloudCluster;
+    }
+
     public String getDefaultLoadCluster() {
         return defaultLoadCluster;
     }
@@ -507,6 +535,13 @@ public class UserProperty implements Writable {
             result.add(Lists.newArrayList(PROP_DEFAULT_LOAD_CLUSTER, ""));
         }
 
+        // default cloud cluster
+        if (defaultCloudCluster != null) {
+            result.add(Lists.newArrayList(DEFAULT_CLOUD_CLUSTER, defaultCloudCluster));
+        } else {
+            result.add(Lists.newArrayList(DEFAULT_CLOUD_CLUSTER, ""));
+        }
+
         for (Map.Entry<String, DppConfig> entry : clusterToDppConfig.entrySet()) {
             String cluster = entry.getKey();
             DppConfig dppConfig = entry.getValue();
@@ -548,36 +583,21 @@ public class UserProperty implements Writable {
     }
 
     public static UserProperty read(DataInput in) throws IOException {
-        UserProperty userProperty = new UserProperty();
-        userProperty.readFields(in);
-        return userProperty;
+        if (Env.getCurrentEnvJournalVersion() < FeMetaVersion.VERSION_130) {
+            UserProperty userProperty = new UserProperty();
+            userProperty.readFields(in);
+            return userProperty;
+        }
+        String json = Text.readString(in);
+        return GsonUtils.GSON.fromJson(json, UserProperty.class);
     }
 
     @Override
     public void write(DataOutput out) throws IOException {
-        // user name
-        Text.writeString(out, qualifiedUser);
-
-        // call UserResource.write(out) to make sure that FE can rollback.
-        UserResource.write(out);
-
-        // load cluster
-        if (defaultLoadCluster == null) {
-            out.writeBoolean(false);
-        } else {
-            out.writeBoolean(true);
-            Text.writeString(out, defaultLoadCluster);
-        }
-        out.writeInt(clusterToDppConfig.size());
-        for (Map.Entry<String, DppConfig> entry : clusterToDppConfig.entrySet()) {
-            Text.writeString(out, entry.getKey());
-            entry.getValue().write(out);
-        }
-
-        // common properties
-        commonProperties.write(out);
+        Text.writeString(out, GsonUtils.GSON.toJson(this));
     }
 
+    @Deprecated
     public void readFields(DataInput in) throws IOException {
         qualifiedUser = Text.readString(in);
         // should be removed after version 3.0
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java
index aaeaa992ab3..1160341df1c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/privilege/UserPropertyMgr.java
@@ -19,23 +19,29 @@ package org.apache.doris.mysql.privilege;
 
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.Env;
+import org.apache.doris.cluster.ClusterNamespace;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
+import org.apache.doris.common.FeMetaVersion;
 import org.apache.doris.common.Pair;
 import org.apache.doris.common.UserException;
+import org.apache.doris.common.io.Text;
 import org.apache.doris.common.io.Writable;
 import org.apache.doris.load.DppConfig;
+import org.apache.doris.persist.gson.GsonUtils;
 import org.apache.doris.resource.Tag;
 
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import com.google.gson.annotations.SerializedName;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -45,12 +51,14 @@ import java.util.concurrent.atomic.AtomicLong;
 public class UserPropertyMgr implements Writable {
     private static final Logger LOG = LogManager.getLogger(UserPropertyMgr.class);
 
+    @SerializedName(value = "propertyMap")
     protected Map<String, UserProperty> propertyMap = Maps.newHashMap();
     public static final String ROOT_USER = "root";
     public static final String SYSTEM_RESOURCE_USER = "system";
     public static final String LDAP_RESOURCE_USER = "ldap";
 
     private static final UserProperty LDAP_PROPERTY = new UserProperty(LDAP_RESOURCE_USER);
+    @SerializedName(value = "resourceVersion")
     private AtomicLong resourceVersion = new AtomicLong(0);
 
     public UserPropertyMgr() {
@@ -119,6 +127,30 @@ public class UserPropertyMgr implements Writable {
         return existProperty.getMaxQueryInstances();
     }
 
+    public String getDefaultCloudCluster(String user) throws DdlException {
+        UserProperty property = propertyMap.get(user);
+        if (property == null) {
+            throw new DdlException("Unknown user(" + user + ")");
+        }
+        return property.getDefaultCloudCluster();
+    }
+
+    public List<String> getCloudClusterUsers(Set<String> users, String clusterName) {
+        List<String> ret = new ArrayList<>();
+        users.forEach(
+                u -> {
+                UserProperty userProperty = propertyMap.get(u);
+                if (userProperty == null) {
+                    return;
+                }
+                if (clusterName.equals(userProperty.getDefaultCloudCluster())) {
+                    ret.add(ClusterNamespace.getNameFromFullName(u));
+                }
+            }
+        );
+        return ret;
+    }
+
     public int getParallelFragmentExecInstanceNum(String qualifiedUser) {
         UserProperty existProperty = propertyMap.get(qualifiedUser);
         existProperty = getLdapPropertyIfNull(qualifiedUser, existProperty);
@@ -219,21 +251,21 @@ public class UserPropertyMgr implements Writable {
     }
 
     public static UserPropertyMgr read(DataInput in) throws IOException {
-        UserPropertyMgr userPropertyMgr = new UserPropertyMgr();
-        userPropertyMgr.readFields(in);
-        return userPropertyMgr;
+        if (Env.getCurrentEnvJournalVersion() < FeMetaVersion.VERSION_130) {
+            UserPropertyMgr userPropertyMgr = new UserPropertyMgr();
+            userPropertyMgr.readFields(in);
+            return userPropertyMgr;
+        }
+        String json = Text.readString(in);
+        return GsonUtils.GSON.fromJson(json, UserPropertyMgr.class);
     }
 
     @Override
     public void write(DataOutput out) throws IOException {
-        out.writeInt(propertyMap.size());
-        for (Map.Entry<String, UserProperty> entry : propertyMap.entrySet()) {
-            entry.getValue().write(out);
-        }
-        // Write resource version
-        out.writeLong(resourceVersion.get());
+        Text.writeString(out, GsonUtils.GSON.toJson(this));
     }
 
+    @Deprecated
     public void readFields(DataInput in) throws IOException {
         int size = in.readInt();
         for (int i = 0; i < size; ++i) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/InsertExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/InsertExecutor.java
index abee2677527..4f182cb6a59 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/InsertExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/InsertExecutor.java
@@ -23,6 +23,7 @@ import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.Database;
 import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.EnvFactory;
 import org.apache.doris.catalog.KeysType;
 import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.catalog.Table;
@@ -123,7 +124,7 @@ public class InsertExecutor {
     public InsertExecutor(ConnectContext ctx, Database database, Table table,
             String labelName, NereidsPlanner planner) {
         this.ctx = ctx;
-        this.coordinator = new Coordinator(ctx, null, planner, ctx.getStatsErrorEstimator());
+        this.coordinator = EnvFactory.getInstance().createCoordinator(ctx, null, planner, ctx.getStatsErrorEstimator());
         this.labelName = labelName;
         this.database = database;
         this.table = table;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java b/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java
index 6cb11f8cb2c..ed264ff7a04 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/PrivInfo.java
@@ -64,6 +64,9 @@ public class PrivInfo implements Writable, GsonPostProcessable {
     @SerializedName(value = "roles")
     private List<String> roles;
 
+    @SerializedName(value = "userId")
+    private String userId;
+
     private PrivInfo() {
 
     }
@@ -150,6 +153,10 @@ public class PrivInfo implements Writable, GsonPostProcessable {
         return role;
     }
 
+    public String getUserId() {
+        return userId;
+    }
+
     public PasswordOptions getPasswordOptions() {
         return passwordOptions == null ? PasswordOptions.UNSET_OPTION : passwordOptions;
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/gson/GsonUtils.java b/fe/fe-core/src/main/java/org/apache/doris/persist/gson/GsonUtils.java
index 65e61c76ac1..43d1b907247 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/persist/gson/GsonUtils.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/gson/GsonUtils.java
@@ -52,7 +52,6 @@ import org.apache.doris.catalog.constraint.PrimaryKeyConstraint;
 import org.apache.doris.catalog.constraint.UniqueConstraint;
 import org.apache.doris.cloud.catalog.CloudPartition;
 import org.apache.doris.cloud.catalog.CloudReplica;
-import org.apache.doris.cloud.datasource.CloudInternalCatalog;
 import org.apache.doris.common.util.RangeUtils;
 import org.apache.doris.datasource.CatalogIf;
 import org.apache.doris.datasource.ExternalDatabase;
@@ -308,13 +307,6 @@ public class GsonUtils {
             .registerSubtype(Partition.class, Partition.class.getSimpleName())
             .registerSubtype(CloudPartition.class, CloudPartition.class.getSimpleName());
 
-    // runtime adapter for class "CloudInternalCatalog".
-    private static RuntimeTypeAdapterFactory<InternalCatalog> internalCatalogTypeAdapterFactory =
-            RuntimeTypeAdapterFactory.of(InternalCatalog.class, "clazz")
-            .registerDefaultSubtype(InternalCatalog.class)
-            .registerSubtype(InternalCatalog.class, InternalCatalog.class.getSimpleName())
-            .registerSubtype(CloudInternalCatalog.class, CloudInternalCatalog.class.getSimpleName());
-
     // the builder of GSON instance.
     // Add any other adapters if necessary.
     private static final GsonBuilder GSON_BUILDER = new GsonBuilder().addSerializationExclusionStrategy(
@@ -333,7 +325,6 @@ public class GsonUtils {
             .registerTypeAdapterFactory(dbTypeAdapterFactory).registerTypeAdapterFactory(tblTypeAdapterFactory)
             .registerTypeAdapterFactory(replicaTypeAdapterFactory)
             .registerTypeAdapterFactory(partitionTypeAdapterFactory)
-            .registerTypeAdapterFactory(internalCatalogTypeAdapterFactory)
             .registerTypeAdapterFactory(partitionInfoTypeAdapterFactory)
             .registerTypeAdapterFactory(hbResponseTypeAdapterFactory)
             .registerTypeAdapterFactory(rdsTypeAdapterFactory)
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
index 02fb26363a2..274725f3f22 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java
@@ -893,6 +893,10 @@ public class OlapScanNode extends ScanNode {
                     }
                     errs.add("replica " + replica.getId() + "'s backend " + replica.getBackendId()
                             + " does not exist or not alive");
+                    errs.add(" or you may not have permission to access the current cluster");
+                    if (ConnectContext.get() != null) {
+                        errs.add("clusterName=" + ConnectContext.get().getCloudCluster());
+                    }
                     continue;
                 }
                 if (!backend.isMixNode()) {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapTableSink.java b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapTableSink.java
index 79335f04583..2c4965c1a80 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/OlapTableSink.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/OlapTableSink.java
@@ -51,6 +51,7 @@ import org.apache.doris.common.Status;
 import org.apache.doris.common.UserException;
 import org.apache.doris.common.util.DebugPointUtil;
 import org.apache.doris.common.util.DebugPointUtil.DebugPoint;
+import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.system.Backend;
 import org.apache.doris.system.SystemInfoService;
 import org.apache.doris.thrift.TColumn;
@@ -494,10 +495,14 @@ public class OlapTableSink extends DataSink {
                 for (Tablet tablet : index.getTablets()) {
                     Multimap<Long, Long> bePathsMap = tablet.getNormalReplicaBackendPathMap();
                     if (bePathsMap.keySet().size() < loadRequiredReplicaNum) {
-                        throw new UserException(InternalErrorCode.REPLICA_FEW_ERR,
-                                "tablet " + tablet.getId() + " alive replica num " + bePathsMap.keySet().size()
-                                        + " < load required replica num " + loadRequiredReplicaNum
-                                        + ", alive backends: [" + StringUtils.join(bePathsMap.keySet(), ",") + "]");
+                        String errMsg = "tablet " + tablet.getId() + " alive replica num " + bePathsMap.keySet().size()
+                                + " < quorum replica num " + loadRequiredReplicaNum
+                                + ", alive backends: [" + StringUtils.join(bePathsMap.keySet(), ",") + "]";
+                        errMsg += " or you may not have permission to access the current cluster";
+                        if (ConnectContext.get() != null) {
+                            errMsg += " clusterName=" + ConnectContext.get().getCloudCluster();
+                        }
+                        throw new UserException(InternalErrorCode.REPLICA_FEW_ERR, errMsg);
                     }
 
                     debugWriteRandomChooseSink(tablet, partition.getVisibleVersion(), bePathsMap);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java
index 484d3c47afb..3e66374e429 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java
@@ -24,6 +24,7 @@ import org.apache.doris.analysis.FloatLiteral;
 import org.apache.doris.analysis.IntLiteral;
 import org.apache.doris.analysis.LiteralExpr;
 import org.apache.doris.analysis.NullLiteral;
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.SetVar;
 import org.apache.doris.analysis.StringLiteral;
 import org.apache.doris.analysis.UserIdentity;
@@ -36,7 +37,6 @@ import org.apache.doris.catalog.Type;
 import org.apache.doris.cloud.system.CloudSystemInfoService;
 import org.apache.doris.cluster.ClusterNamespace;
 import org.apache.doris.common.Config;
-import org.apache.doris.common.ErrorCode;
 import org.apache.doris.common.util.DebugUtil;
 import org.apache.doris.common.util.TimeUtils;
 import org.apache.doris.datasource.CatalogIf;
@@ -48,6 +48,7 @@ import org.apache.doris.mysql.MysqlChannel;
 import org.apache.doris.mysql.MysqlCommand;
 import org.apache.doris.mysql.MysqlSslContext;
 import org.apache.doris.mysql.ProxyMysqlChannel;
+import org.apache.doris.mysql.privilege.PrivPredicate;
 import org.apache.doris.nereids.StatementContext;
 import org.apache.doris.nereids.stats.StatsErrorEstimator;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
@@ -1091,8 +1092,10 @@ public class ConnectContext {
 
         if (Strings.isNullOrEmpty(cluster)) {
             LOG.warn("cant get a valid cluster for user {} to use", getCurrentUserIdentity());
+            /*
             getState().setError(ErrorCode.ERR_NO_CLUSTER_ERROR,
                     "Cant get a Valid cluster for you to use, plz connect admin");
+             */
         } else {
             this.cloudCluster = cluster;
             LOG.info("finally set context cluster name {}", cloudCluster);
@@ -1110,6 +1113,10 @@ public class ConnectContext {
         List<String> cloudClusterNames = ((CloudSystemInfoService) Env.getCurrentSystemInfo()).getCloudClusterNames();
         // get all available cluster of the user
         for (String cloudClusterName : cloudClusterNames) {
+            if (!Env.getCurrentEnv().getAuth().checkCloudPriv(getCurrentUserIdentity(),
+                    cloudClusterName, PrivPredicate.USAGE, ResourceTypeEnum.CLUSTER)) {
+                continue;
+            }
             // find a cluster has more than one alive be
             List<Backend> bes = ((CloudSystemInfoService) Env.getCurrentSystemInfo())
                     .getBackendsByClusterName(cloudClusterName);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java
index 6fd2abbf5f9..a7a45b4baa6 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java
@@ -28,6 +28,7 @@ import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.DatabaseIf;
 import org.apache.doris.catalog.Env;
 import org.apache.doris.catalog.TableIf;
+import org.apache.doris.cloud.catalog.CloudEnv;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.Config;
 import org.apache.doris.common.DdlException;
@@ -112,6 +113,19 @@ public abstract class ConnectProcessor {
             return;
         }
 
+        //  mysql client
+        if (Config.isCloudMode()) {
+            try {
+                dbName = ((CloudEnv) ctx.getEnv()).analyzeCloudCluster(dbName, ctx);
+            } catch (DdlException e) {
+                ctx.getState().setError(e.getMysqlErrorCode(), e.getMessage());
+                return;
+            }
+            if (dbName == null || dbName.isEmpty()) {
+                return;
+            }
+        }
+
         // check catalog and db exists
         if (catalogName != null) {
             CatalogIf catalogIf = ctx.getEnv().getCatalogMgr().getCatalogNullable(catalogName);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java b/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java
index 5c24670fee1..17e62d40f1f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/Coordinator.java
@@ -167,7 +167,7 @@ public class Coordinator implements CoordInterface {
     // save of related backends of this query
     Map<TNetworkAddress, Long> addressToBackendID = Maps.newHashMap();
 
-    private ImmutableMap<Long, Backend> idToBackend = ImmutableMap.of();
+    protected ImmutableMap<Long, Backend> idToBackend = ImmutableMap.of();
 
     // copied from TQueryExecRequest; constant across all fragments
     private final TDescriptorTable descTable;
@@ -507,7 +507,7 @@ public class Coordinator implements CoordInterface {
     }
 
     // Initialize
-    private void prepare() {
+    protected void prepare() {
         for (PlanFragment fragment : fragments) {
             fragmentExecParamsMap.put(fragment.getFragmentId(), new FragmentExecParams(fragment));
         }
@@ -571,7 +571,7 @@ public class Coordinator implements CoordInterface {
         }
     }
 
-    private void processFragmentAssignmentAndParams() throws Exception {
+    protected void processFragmentAssignmentAndParams() throws Exception {
         // prepare information
         prepare();
         // compute Fragment Instance
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java
index f0617f8bdf4..34744f0c67b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java
@@ -18,12 +18,14 @@
 package org.apache.doris.qe;
 
 import org.apache.doris.analysis.AdminCopyTabletStmt;
+import org.apache.doris.analysis.CompoundPredicate.Operator;
 import org.apache.doris.analysis.DescribeStmt;
 import org.apache.doris.analysis.DiagnoseTabletStmt;
 import org.apache.doris.analysis.Expr;
 import org.apache.doris.analysis.HelpStmt;
 import org.apache.doris.analysis.LimitElement;
 import org.apache.doris.analysis.PartitionNames;
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.ShowAlterStmt;
 import org.apache.doris.analysis.ShowAnalyzeStmt;
 import org.apache.doris.analysis.ShowAnalyzeTaskStatus;
@@ -34,6 +36,7 @@ import org.apache.doris.analysis.ShowBrokerStmt;
 import org.apache.doris.analysis.ShowBuildIndexStmt;
 import org.apache.doris.analysis.ShowCatalogRecycleBinStmt;
 import org.apache.doris.analysis.ShowCatalogStmt;
+import org.apache.doris.analysis.ShowClusterStmt;
 import org.apache.doris.analysis.ShowCollationStmt;
 import org.apache.doris.analysis.ShowColumnHistStmt;
 import org.apache.doris.analysis.ShowColumnStatsStmt;
@@ -140,6 +143,7 @@ import org.apache.doris.catalog.TabletInvertedIndex;
 import org.apache.doris.catalog.TabletMeta;
 import org.apache.doris.catalog.View;
 import org.apache.doris.clone.DynamicPartitionScheduler;
+import org.apache.doris.cloud.system.CloudSystemInfoService;
 import org.apache.doris.cluster.ClusterNamespace;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.CaseSensibility;
@@ -193,7 +197,10 @@ import org.apache.doris.load.LoadJob;
 import org.apache.doris.load.LoadJob.JobState;
 import org.apache.doris.load.loadv2.LoadManager;
 import org.apache.doris.load.routineload.RoutineLoadJob;
+import org.apache.doris.mysql.privilege.Auth;
+import org.apache.doris.mysql.privilege.PrivBitSet;
 import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.mysql.privilege.Privilege;
 import org.apache.doris.statistics.AnalysisInfo;
 import org.apache.doris.statistics.ColumnStatistic;
 import org.apache.doris.statistics.Histogram;
@@ -220,6 +227,7 @@ import org.apache.doris.thrift.TUnit;
 import org.apache.doris.transaction.GlobalTransactionMgrIface;
 import org.apache.doris.transaction.TransactionStatus;
 
+import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSetMultimap;
@@ -443,6 +451,8 @@ public class ShowExecutor {
             handleShowAnalyzeTaskStatus();
         } else if (stmt instanceof ShowConvertLSCStmt) {
             handleShowConvertLSC();
+        } else if (stmt instanceof ShowClusterStmt) {
+            handleShowCluster();
         } else {
             handleEmtpy();
         }
@@ -730,6 +740,44 @@ public class ShowExecutor {
         resultSet = new ShowResultSet(metaData, finalRows);
     }
 
+    // Show clusters
+    private void handleShowCluster() {
+        final ShowClusterStmt showStmt = (ShowClusterStmt) stmt;
+        final List<List<String>> rows = Lists.newArrayList();
+        List<String> clusterNames = null;
+        clusterNames = ((CloudSystemInfoService) Env.getCurrentSystemInfo()).getCloudClusterNames();
+
+        final Set<String> clusterNameSet = Sets.newTreeSet();
+        clusterNameSet.addAll(clusterNames);
+
+        for (String clusterName : clusterNameSet) {
+            ArrayList<String> row = Lists.newArrayList(clusterName);
+            // current_used, users
+            if (!Env.getCurrentEnv().getAuth()
+                    .checkCloudPriv(ConnectContext.get().getCurrentUserIdentity(), clusterName,
+                    PrivPredicate.USAGE, ResourceTypeEnum.CLUSTER)) {
+                continue;
+            }
+            row.add(clusterName.equals(ctx.getCloudCluster()) ? "TRUE" : "FALSE");
+            List<String> users = Env.getCurrentEnv().getAuth().getCloudClusterUsers(clusterName);
+            // non-root do not display root information
+            if (!Auth.ROOT_USER.equals(ctx.getQualifiedUser())) {
+                users.remove(Auth.ROOT_USER);
+            }
+            // common user, not admin
+            if (!Env.getCurrentEnv().getAuth().checkGlobalPriv(ConnectContext.get().currentUserIdentity,
+                    PrivPredicate.of(PrivBitSet.of(Privilege.ADMIN_PRIV), Operator.OR))) {
+                users.removeIf(user -> !user.equals(ClusterNamespace.getNameFromFullName(ctx.getQualifiedUser())));
+            }
+            String result = Joiner.on(", ").join(users);
+            row.add(result);
+            rows.add(row);
+        }
+
+        resultSet = new ShowResultSet(showStmt.getMetaData(), rows);
+    }
+
+
     private void handleShowDbId() {
         ShowDbIdStmt showStmt = (ShowDbIdStmt) stmt;
         long dbId = showStmt.getDbId();
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
index 7879f5ed86f..5dfa49ddf38 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
@@ -80,6 +80,7 @@ import org.apache.doris.analysis.UseStmt;
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.Database;
 import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.EnvFactory;
 import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.catalog.PrimitiveType;
 import org.apache.doris.catalog.ScalarType;
@@ -87,6 +88,8 @@ import org.apache.doris.catalog.Table;
 import org.apache.doris.catalog.TableIf;
 import org.apache.doris.catalog.TableIf.TableType;
 import org.apache.doris.catalog.Type;
+import org.apache.doris.cloud.analysis.UseCloudClusterStmt;
+import org.apache.doris.cloud.catalog.CloudEnv;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.AuditLog;
 import org.apache.doris.common.Config;
@@ -800,6 +803,9 @@ public class StmtExecutor {
                 handleSwitchStmt();
             } else if (parsedStmt instanceof UseStmt) {
                 handleUseStmt();
+            }  else if (parsedStmt instanceof UseCloudClusterStmt) {
+                // jdbc client use
+                handleUseCloudClusterStmt();
             } else if (parsedStmt instanceof TransactionStmt) {
                 handleTransactionStmt();
             } else if (parsedStmt instanceof CreateTableAsSelectStmt) {
@@ -1570,7 +1576,8 @@ public class StmtExecutor {
         if (queryStmt instanceof SelectStmt && ((SelectStmt) parsedStmt).isPointQueryShortCircuit()) {
             coordBase = new PointQueryExec(planner, analyzer);
         } else {
-            coord = new Coordinator(context, analyzer, planner, context.getStatsErrorEstimator());
+            coord =  EnvFactory.getInstance().createCoordinator(context, analyzer,
+                planner, context.getStatsErrorEstimator());
             QeProcessorImpl.INSTANCE.registerQuery(context.queryId(),
                     new QeProcessorImpl.QueryInfo(context, originStmt.originStmt, coord));
             profile.setExecutionProfile(coord.getExecutionProfile());
@@ -2047,7 +2054,8 @@ public class StmtExecutor {
             LOG.info("Do insert [{}] with query id: {}", label, DebugUtil.printId(context.queryId()));
 
             try {
-                coord = new Coordinator(context, analyzer, planner, context.getStatsErrorEstimator());
+                coord = EnvFactory.getInstance().createCoordinator(context, analyzer,
+                        planner, context.getStatsErrorEstimator());
                 coord.setLoadZeroTolerance(context.getSessionVariable().getEnableInsertStrict());
                 coord.setQueryType(TQueryType.LOAD);
                 profile.setExecutionProfile(coord.getExecutionProfile());
@@ -2286,6 +2294,32 @@ public class StmtExecutor {
         context.getState().setOk();
     }
 
+    private void handleUseCloudClusterStmt() throws AnalysisException {
+        UseCloudClusterStmt useCloudClusterStmt = (UseCloudClusterStmt) parsedStmt;
+        try {
+            ((CloudEnv) context.getEnv()).changeCloudCluster(useCloudClusterStmt.getCluster(), context);
+        } catch (DdlException e) {
+            context.getState().setError(e.getMysqlErrorCode(), e.getMessage());
+            return;
+        }
+
+        if (Strings.isNullOrEmpty(useCloudClusterStmt.getDatabase())) {
+            return;
+        }
+
+        try {
+            if (useCloudClusterStmt.getCatalogName() != null) {
+                context.getEnv().changeCatalog(context, useCloudClusterStmt.getCatalogName());
+            }
+            context.getEnv().changeDb(context, useCloudClusterStmt.getDatabase());
+        } catch (DdlException e) {
+            context.getState().setError(e.getMysqlErrorCode(), e.getMessage());
+            return;
+        }
+
+        context.getState().setOk();
+    }
+
     private void sendMetaData(ResultSetMetaData metaData) throws IOException {
         Preconditions.checkState(context.getConnectType() == ConnectType.MYSQL);
         // sends how many columns
@@ -2851,7 +2885,8 @@ public class StmtExecutor {
                 throw new RuntimeException("Failed to execute internal SQL. " + Util.getRootCauseMessage(e), e);
             }
             RowBatch batch;
-            coord = new Coordinator(context, analyzer, planner, context.getStatsErrorEstimator());
+            coord =  EnvFactory.getInstance().createCoordinator(context, analyzer,
+                    planner, context.getStatsErrorEstimator());
             profile.setExecutionProfile(coord.getExecutionProfile());
             try {
                 QeProcessorImpl.INSTANCE.registerQuery(context.queryId(),
diff --git a/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java b/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
index cf7533cbc70..73dc65db522 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
@@ -36,6 +36,7 @@ import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.Database;
 import org.apache.doris.catalog.DatabaseIf;
 import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.EnvFactory;
 import org.apache.doris.catalog.MaterializedIndex;
 import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.catalog.Partition;
@@ -2053,7 +2054,7 @@ public class FrontendServiceImpl implements FrontendService.Iface {
             TQueryOptions tQueryOptions = ctx.getSessionVariable().toThrift();
             executor.analyze(tQueryOptions);
             Analyzer analyzer = new Analyzer(ctx.getEnv(), ctx);
-            Coordinator coord = new Coordinator(ctx, analyzer, executor.planner());
+            Coordinator coord =  EnvFactory.getInstance().createCoordinator(ctx, analyzer, executor.planner(), null);
             coord.setLoadMemLimit(request.getExecMemLimit());
             coord.setQueryType(TQueryType.LOAD);
 
diff --git a/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java b/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java
index 985cce34686..382e9348b79 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/GrantStmtTest.java
@@ -99,12 +99,14 @@ public class GrantStmtTest {
     public void testResourceNormal() throws UserException {
         String resourceName = "spark0";
         List<AccessPrivilegeWithCols> privileges = Lists.newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.USAGE_PRIV));
-        GrantStmt stmt = new GrantStmt(new UserIdentity("testUser", "%"), null, new ResourcePattern(resourceName), privileges);
+        GrantStmt stmt = new GrantStmt(new UserIdentity("testUser", "%"), null,
+                new ResourcePattern(resourceName, ResourceTypeEnum.GENERAL), privileges, ResourceTypeEnum.GENERAL);
         stmt.analyze(analyzer);
         Assert.assertEquals(resourceName, stmt.getResourcePattern().getResourceName());
         Assert.assertEquals(Auth.PrivLevel.RESOURCE, stmt.getResourcePattern().getPrivLevel());
 
-        stmt = new GrantStmt(new UserIdentity("testUser", "%"), null, new ResourcePattern("*"), privileges);
+        stmt = new GrantStmt(new UserIdentity("testUser", "%"), null,
+                new ResourcePattern("*", ResourceTypeEnum.GENERAL), privileges, ResourceTypeEnum.GENERAL);
         stmt.analyze(analyzer);
         Assert.assertEquals(Auth.PrivLevel.GLOBAL, stmt.getResourcePattern().getPrivLevel());
         Assert.assertEquals("GRANT Usage_priv ON RESOURCE '*' TO 'testUser'@'%'", stmt.toSql());
diff --git a/fe/fe-core/src/test/java/org/apache/doris/datasource/ColumnPrivTest.java b/fe/fe-core/src/test/java/org/apache/doris/datasource/ColumnPrivTest.java
index 8e8bbd11100..690649fae7d 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/datasource/ColumnPrivTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/datasource/ColumnPrivTest.java
@@ -25,6 +25,7 @@ import org.apache.doris.analysis.CreateUserStmt;
 import org.apache.doris.analysis.CreateViewStmt;
 import org.apache.doris.analysis.DropCatalogStmt;
 import org.apache.doris.analysis.GrantStmt;
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.ShowCatalogStmt;
 import org.apache.doris.analysis.ShowTableStatusStmt;
 import org.apache.doris.analysis.UserIdentity;
@@ -314,6 +315,11 @@ public class ColumnPrivTest extends TestWithFeService {
                     }
                 }
             }
+
+            @Override
+            public boolean checkCloudPriv(UserIdentity currentUser, String resourceName, PrivPredicate wanted, ResourceTypeEnum type) {
+                return false;
+            }
         }
     }
 
diff --git a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java
index 8f1dde6eac7..d0234c01ef8 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/privilege/AuthTest.java
@@ -24,6 +24,7 @@ import org.apache.doris.analysis.DropRoleStmt;
 import org.apache.doris.analysis.DropUserStmt;
 import org.apache.doris.analysis.GrantStmt;
 import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.RevokeStmt;
 import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserDesc;
@@ -1682,9 +1683,9 @@ public class AuthTest {
         UserIdentity userIdentity = new UserIdentity("testUser", "%");
         String role = "role0";
         String resourceName = "spark0";
-        ResourcePattern resourcePattern = new ResourcePattern(resourceName);
+        ResourcePattern resourcePattern = new ResourcePattern(resourceName, ResourceTypeEnum.GENERAL);
         String anyResource = "*";
-        ResourcePattern anyResourcePattern = new ResourcePattern(anyResource);
+        ResourcePattern anyResourcePattern = new ResourcePattern(anyResource, ResourceTypeEnum.GENERAL);
         List<AccessPrivilegeWithCols> usagePrivileges = Lists
                 .newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.USAGE_PRIV));
         UserDesc userDesc = new UserDesc(userIdentity, "12345", true);
@@ -1701,7 +1702,7 @@ public class AuthTest {
         }
 
         // 2. grant usage_priv on resource 'spark0' to 'testUser'@'%'
-        GrantStmt grantStmt = new GrantStmt(userIdentity, null, resourcePattern, usagePrivileges);
+        GrantStmt grantStmt = new GrantStmt(userIdentity, null, resourcePattern, usagePrivileges, ResourceTypeEnum.GENERAL);
         try {
             grantStmt.analyze(analyzer);
             auth.grant(grantStmt);
@@ -1713,7 +1714,7 @@ public class AuthTest {
         Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, PrivPredicate.USAGE));
 
         // 3. revoke usage_priv on resource 'spark0' from 'testUser'@'%'
-        RevokeStmt revokeStmt = new RevokeStmt(userIdentity, null, resourcePattern, usagePrivileges);
+        RevokeStmt revokeStmt = new RevokeStmt(userIdentity, null, resourcePattern, usagePrivileges, ResourceTypeEnum.GENERAL);
         try {
             revokeStmt.analyze(analyzer);
             auth.revoke(revokeStmt);
@@ -1728,7 +1729,7 @@ public class AuthTest {
             List<AccessPrivilegeWithCols> notAllowedPrivileges = Lists
                     .newArrayList(new AccessPrivilegeWithCols(
                             AccessPrivilege.fromName(Privilege.notBelongToResourcePrivileges[i].getName())));
-            grantStmt = new GrantStmt(userIdentity, null, resourcePattern, notAllowedPrivileges);
+            grantStmt = new GrantStmt(userIdentity, null, resourcePattern, notAllowedPrivileges, ResourceTypeEnum.GENERAL);
             try {
                 grantStmt.analyze(analyzer);
                 Assert.fail(String.format("Can not grant/revoke %s to/from any other users or roles",
@@ -1758,7 +1759,7 @@ public class AuthTest {
             Assert.fail();
         }
         // grant usage_priv on resource 'spark0' to role 'role0'
-        grantStmt = new GrantStmt(null, role, resourcePattern, usagePrivileges);
+        grantStmt = new GrantStmt(null, role, resourcePattern, usagePrivileges, ResourceTypeEnum.GENERAL);
         try {
             grantStmt.analyze(analyzer);
             auth.grant(grantStmt);
@@ -1780,7 +1781,7 @@ public class AuthTest {
         Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, PrivPredicate.USAGE));
 
         // 3. revoke usage_priv on resource 'spark0' from role 'role0'
-        revokeStmt = new RevokeStmt(null, role, resourcePattern, usagePrivileges);
+        revokeStmt = new RevokeStmt(null, role, resourcePattern, usagePrivileges, ResourceTypeEnum.GENERAL);
         try {
             revokeStmt.analyze(analyzer);
             auth.revoke(revokeStmt);
@@ -1822,7 +1823,7 @@ public class AuthTest {
         }
 
         // 2. grant usage_priv on resource '*' to 'testUser'@'%'
-        grantStmt = new GrantStmt(userIdentity, null, anyResourcePattern, usagePrivileges);
+        grantStmt = new GrantStmt(userIdentity, null, anyResourcePattern, usagePrivileges, ResourceTypeEnum.GENERAL);
         try {
             grantStmt.analyze(analyzer);
             auth.grant(grantStmt);
@@ -1836,7 +1837,7 @@ public class AuthTest {
         Assert.assertFalse(accessManager.checkGlobalPriv(userIdentity, PrivPredicate.SHOW));
 
         // 3. revoke usage_priv on resource '*' from 'testUser'@'%'
-        revokeStmt = new RevokeStmt(userIdentity, null, anyResourcePattern, usagePrivileges);
+        revokeStmt = new RevokeStmt(userIdentity, null, anyResourcePattern, usagePrivileges, ResourceTypeEnum.GENERAL);
         try {
             revokeStmt.analyze(analyzer);
             auth.revoke(revokeStmt);
@@ -1870,7 +1871,7 @@ public class AuthTest {
             Assert.fail();
         }
         // grant usage_priv on resource '*' to role 'role0'
-        grantStmt = new GrantStmt(null, role, anyResourcePattern, usagePrivileges);
+        grantStmt = new GrantStmt(null, role, anyResourcePattern, usagePrivileges, ResourceTypeEnum.GENERAL);
         try {
             grantStmt.analyze(analyzer);
             auth.grant(grantStmt);
@@ -1892,7 +1893,7 @@ public class AuthTest {
         Assert.assertTrue(accessManager.checkGlobalPriv(userIdentity, PrivPredicate.USAGE));
 
         // 3. revoke usage_priv on resource '*' from role 'role0'
-        revokeStmt = new RevokeStmt(null, role, anyResourcePattern, usagePrivileges);
+        revokeStmt = new RevokeStmt(null, role, anyResourcePattern, usagePrivileges, ResourceTypeEnum.GENERAL);
         try {
             revokeStmt.analyze(analyzer);
             auth.revoke(revokeStmt);
@@ -1936,7 +1937,7 @@ public class AuthTest {
         // 1. grant db table priv to resource
         List<AccessPrivilegeWithCols> privileges = Lists
                 .newArrayList(new AccessPrivilegeWithCols(AccessPrivilege.SELECT_PRIV));
-        grantStmt = new GrantStmt(userIdentity, null, resourcePattern, privileges);
+        grantStmt = new GrantStmt(userIdentity, null, resourcePattern, privileges, ResourceTypeEnum.GENERAL);
         hasException = false;
         try {
             grantStmt.analyze(analyzer);
diff --git a/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java b/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java
index 5e02244afac..25c6a787740 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/persist/PrivInfoTest.java
@@ -19,6 +19,7 @@ package org.apache.doris.persist;
 
 import org.apache.doris.analysis.PasswordOptions;
 import org.apache.doris.analysis.ResourcePattern;
+import org.apache.doris.analysis.ResourceTypeEnum;
 import org.apache.doris.analysis.TablePattern;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.common.FeMetaVersion;
@@ -98,7 +99,7 @@ public class PrivInfoTest {
 
     @Test
     public void testWithResourcePattern() throws IOException {
-        PrivInfo privInfo = new PrivInfo(UserIdentity.ROOT, new ResourcePattern("res1"),
+        PrivInfo privInfo = new PrivInfo(UserIdentity.ROOT, new ResourcePattern("res1", ResourceTypeEnum.GENERAL),
                 PrivBitSet.of(Privilege.DROP_PRIV), new byte[] {'a', 'b', 'c'}, "role");
 
         // 1. Write objects to file
diff --git a/fe/fe-core/src/test/java/org/apache/doris/planner/ResourceTagQueryTest.java b/fe/fe-core/src/test/java/org/apache/doris/planner/ResourceTagQueryTest.java
index 5afe9c3b39b..55ee219db61 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/planner/ResourceTagQueryTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/planner/ResourceTagQueryTest.java
@@ -280,7 +280,7 @@ public class ResourceTagQueryTest {
         Assert.assertEquals(1000000, execMemLimit);
 
         List<List<String>> userProps = Env.getCurrentEnv().getAuth().getUserProperties(Auth.ROOT_USER);
-        Assert.assertEquals(11, userProps.size());
+        Assert.assertEquals(12, userProps.size());
 
         // now :
         // be1 be2 be3 ==>tag1;
diff --git a/fe/fe-core/src/test/java/org/apache/doris/qe/CoordinatorTest.java b/fe/fe-core/src/test/java/org/apache/doris/qe/CoordinatorTest.java
index ca8109e40e3..b47bdbb7854 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/qe/CoordinatorTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/qe/CoordinatorTest.java
@@ -25,6 +25,7 @@ import org.apache.doris.analysis.TableRef;
 import org.apache.doris.analysis.TupleDescriptor;
 import org.apache.doris.analysis.TupleId;
 import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.EnvFactory;
 import org.apache.doris.catalog.HashDistributionInfo;
 import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.common.jmockit.Deencapsulation;
@@ -88,7 +89,7 @@ public class CoordinatorTest extends Coordinator {
 
     @Test
     public void testComputeColocateJoinInstanceParam()  {
-        Coordinator coordinator = new Coordinator(context, analyzer, originalPlanner);
+        Coordinator coordinator =  EnvFactory.getInstance().createCoordinator(context, analyzer, originalPlanner, null);
 
         PlanFragmentId planFragmentId = new PlanFragmentId(1);
         int scanNodeId = 1;
@@ -288,7 +289,7 @@ public class CoordinatorTest extends Coordinator {
 
     @Test
     public void testColocateJoinAssignment()  {
-        Coordinator coordinator = new Coordinator(context, analyzer, originalPlanner);
+        Coordinator coordinator =  EnvFactory.getInstance().createCoordinator(context, analyzer, originalPlanner, null);
 
         PlanFragmentId planFragmentId = new PlanFragmentId(1);
         int scanNodeId = 1;
@@ -514,7 +515,7 @@ public class CoordinatorTest extends Coordinator {
 
     @Test
     public void testComputeScanRangeAssignmentByScheduler()  {
-        Coordinator coordinator = new Coordinator(context, analyzer, originalPlanner);
+        Coordinator coordinator =  EnvFactory.getInstance().createCoordinator(context, analyzer, originalPlanner, null);
         PlanFragmentId planFragmentId = new PlanFragmentId(1);
         int scanNodeId = 1;
         Map<PlanFragmentId, Set<Integer>> fragmentIdToScanNodeIds = new HashMap<>();
@@ -605,7 +606,7 @@ public class CoordinatorTest extends Coordinator {
 
     @Test
     public void testGetExecHostPortForFragmentIDAndBucketSeq()  {
-        Coordinator coordinator = new Coordinator(context, analyzer, originalPlanner);
+        Coordinator coordinator = EnvFactory.getInstance().createCoordinator(context, analyzer, originalPlanner, null);
         PlanFragmentId planFragmentId = new PlanFragmentId(1);
         // each olaptable bucket have the same TScanRangeLocations, be id is {0, 1, 2}
         TScanRangeLocations tScanRangeLocations = new TScanRangeLocations();
@@ -735,7 +736,7 @@ public class CoordinatorTest extends Coordinator {
 
     @Test
     public void testComputeScanRangeAssignment()  {
-        Coordinator coordinator = new Coordinator(context, analyzer, originalPlanner);
+        Coordinator coordinator =  EnvFactory.getInstance().createCoordinator(context, analyzer, originalPlanner, null);
 
         //TScanRangeLocations
         TScanRangeLocations tScanRangeLocations = new TScanRangeLocations();
diff --git a/regression-test/suites/account_p0/test_auth_compatibility.groovy b/regression-test/suites/account_p0/test_auth_compatibility.groovy
new file mode 100644
index 00000000000..c4f780b0175
--- /dev/null
+++ b/regression-test/suites/account_p0/test_auth_compatibility.groovy
@@ -0,0 +1,91 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_auth_compatibility", "account") {
+    def user = 'acount_auth_compatibility_user'
+    def pwd = 'C123_567p'
+    def dbName = 'account_auth_compatibility_db'
+    def tableName = 'account_auth_compatibility_table'
+    def viewName = 'account_auth_compatibility_table_view'
+
+    def getProperty = { property, userName ->
+        def result = null
+        if (userName == "") {
+            result = sql_return_maparray """SHOW PROPERTY""" 
+        } else {
+            result = sql_return_maparray """SHOW PROPERTY FOR '${userName}'""" 
+        }
+        result.find {
+            it.Key == property as String
+        }
+    }
+
+    // not drop user, db, table for test compatibility
+    sql """CREATE USER IF NOT EXISTS '${user}'"""
+    sql """SET PASSWORD FOR '${user}' = PASSWORD('${pwd}')"""
+    sql """CREATE DATABASE IF NOT EXISTS ${dbName}"""
+    sql """USE ${dbName}"""
+    sql """
+        CREATE TABLE IF NOT EXISTS ${dbName}.${tableName}
+        (
+        k1 TINYINT,
+        k2 DECIMAL(10, 2) DEFAULT "10.5",
+        v1 HLL HLL_UNION,
+        v2 BITMAP BITMAP_UNION
+        )
+        ENGINE=olap
+        AGGREGATE KEY(k1, k2)
+        DISTRIBUTED BY HASH(k1) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1"
+        )
+    """
+
+    sql """
+        CREATE VIEW IF NOT EXISTS ${dbName}.${viewName} (k1, k2) AS SELECT k1, k2  FROM ${dbName}.${tableName} WHERE k1 = 20160112 GROUP BY k1,k2;
+    """
+    sql """
+        GRANT SELECT_PRIV on *.*.* to ${user};
+    """
+
+    sql """
+        GRANT SHOW_VIEW_PRIV on ${dbName}.${viewName} TO ${user}
+    """
+
+    connect(user = user, password = pwd, url = context.config.jdbcUrl) {
+        sql """USE ${dbName}"""
+        // auth ok, no exception
+        sql """SHOW CREATE TABLE ${viewName}"""
+    }
+
+    sql """SET PROPERTY FOR '${user}' 'max_user_connections'= '2048'"""
+
+    sql """SET PROPERTY FOR '${user}'
+    'load_cluster.cluster1.hadoop_palo_path' = '/user/doris/doris_path',
+    'load_cluster.cluster1.hadoop_configs' = 'fs.default.name=hdfs://dpp.cluster.com:port;mapred.job.tracker=dpp.cluster.com:port;hadoop.job.ugi=user,password;mapred.job.queue.name=job_queue_name_in_hadoop;mapred.job.priority=HIGH;';
+    """
+    sql """SET PROPERTY FOR '${user}' 'default_load_cluster' = 'cluster1'"""
+
+    def result = getProperty("max_user_connections", "${user}")
+    assertEquals(result.Value as String, "2048" as String)
+
+    result = getProperty("default_load_cluster", "${user}")
+    assertEquals(result.Value as String, "cluster1" as String)
+
+    result = getProperty("load_cluster.cluster1.hadoop_palo_path", "${user}")
+    assertEquals(result.Value as String, "/user/doris/doris_path" as String)
+}
diff --git a/regression-test/suites/cloud/auth/test_grant_revoke_cluster_role.groovy b/regression-test/suites/cloud/auth/test_grant_revoke_cluster_role.groovy
new file mode 100644
index 00000000000..1c5567383b5
--- /dev/null
+++ b/regression-test/suites/cloud/auth/test_grant_revoke_cluster_role.groovy
@@ -0,0 +1,265 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_grant_revoke_cluster_to_role", "cloud_auth") {
+    def isCloudMode = {
+        def ret = sql_return_maparray  """show backends"""
+        ret.Tag[0].contains("cloud_cluster_name")
+    }
+    def cloudMode = isCloudMode.call()
+    log.info("is cloud mode $cloudMode")
+    if (!cloudMode) {
+        return
+    }
+    def roleName = "testRole"
+    def user1 = "test_grant_revoke_cluster_to_user1"
+    def tbl = "test_auth_role_tbl"
+    def testClusterA = "clusterA"
+    sql """drop table if exists ${tbl}"""
+
+    sql """drop user if exists ${user1}"""
+    sql """
+         drop role if exists ${roleName}
+        """
+
+    def showRoles = { name ->
+        def ret = sql_return_maparray """show roles"""
+        ret.find {
+            def matcher = it.Name =~ /.*${name}$/
+            matcher.matches()
+        }
+    }
+
+    def getCluster = { cluster ->
+        def result = sql " SHOW CLUSTERS; "
+        for (int i = 0; i < result.size(); i++) {
+            if (result[i][0] == cluster) {
+                return result[i]
+            }
+        }
+        return null
+    }
+
+    def commonAuth = { result, UserIdentity, Password, Roles, GlobalPrivs ->
+        assertEquals(UserIdentity as String, result.UserIdentity[0] as String)
+        assertEquals(Password as String, result.Password[0] as String)
+        assertEquals(Roles as String, result.Roles[0] as String)
+        assertEquals(GlobalPrivs as String, result.GlobalPrivs[0] as String)
+    }
+    
+    def fieldDisorder = { result, expected1, expected2 ->
+        boolean ret = false
+        if ((result as String) == (expected1 as String) || (result as String) == (expected2 as String)) {
+            ret = true
+        }
+        return ret
+    }
+
+    def clusters = sql " SHOW CLUSTERS; "
+    assertTrue(!clusters.isEmpty())
+    def validCluster = clusters[0][0]
+
+    // 1. create role
+    sql """
+        create role ${roleName}
+        """
+
+    def result = showRoles.call(roleName)
+    assertNull(result.CloudClusterPrivs)
+    assertEquals(result.Users, "")
+
+    // grant cluster usage_priv to role
+    sql """
+        grant usage_priv on cluster '$testClusterA' to role "${roleName}";
+        """
+    result = showRoles.call(roleName)
+    assertEquals(result.CloudClusterPrivs as String, "$testClusterA: Cluster_Usage_priv " as String)
+    assertEquals(result.Users as String, "")
+
+    sql """
+        create user "${user1}" identified by 'Cloud12345' default role "${roleName}"
+        """
+    
+    sql """
+        GRANT USAGE_PRIV ON CLUSTER '${validCluster}' TO ROLE '${roleName}'
+    """
+
+    sql """
+        GRANT SELECT_PRIV ON *.*.* TO ROLE '${roleName}'
+    """
+    result = sql_return_maparray """show grants for '${user1}'"""
+    commonAuth result, "'${user1}'@'%'", "Yes", "testRole", "Select_priv "
+    assertEquals("[$testClusterA: Cluster_Usage_priv ; $validCluster: Cluster_Usage_priv ]" as String, result.CloudClusterPrivs as String)
+    def db = context.dbName
+
+    sql """
+    CREATE TABLE ${tbl} (
+    `k1` int(11) NULL,
+    `k2` char(5) NULL
+    )
+    DUPLICATE KEY(`k1`, `k2`)
+    COMMENT 'OLAP'
+    DISTRIBUTED BY HASH(`k1`) BUCKETS 1
+    PROPERTIES (
+    "replication_num"="1"
+    );
+    """
+
+    sql """
+    set enable_memtable_on_sink_node = false
+    """
+
+    sql """
+        insert into ${tbl} (k1, k2) values (1, "10");
+    """
+
+    connect(user = "${user1}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+        sql """use @${validCluster}"""
+        def sqlRet = sql """SELECT * FROM ${db}.${tbl}"""
+        assertEquals(sqlRet[0][0] as int, 1)
+        assertEquals(sqlRet[0][1] as int, 10)
+    }
+
+    // grant * to role
+    sql """
+        grant usage_priv on cluster * to role "${roleName}";
+        """
+
+    result = showRoles.call(roleName)
+    assertTrue(fieldDisorder.call(result.CloudClusterPrivs as String,
+        "$testClusterA: Cluster_Usage_priv ; $validCluster: Cluster_Usage_priv " as String, 
+        "$validCluster: Cluster_Usage_priv ; $testClusterA: Cluster_Usage_priv " as String) as boolean)
+    assertEquals(result.GlobalPrivs as String, "Select_priv  Cluster_Usage_priv " as String)
+    def matcher = result.Users =~ /.*${user1}.*/
+    assertTrue(matcher.matches())
+
+
+    result = sql_return_maparray """show grants for '${user1}'"""
+    commonAuth result, "'${user1}'@'%'", "Yes", "testRole", "Select_priv Cluster_Usage_priv "
+    assertEquals("[$testClusterA: Cluster_Usage_priv ; $validCluster: Cluster_Usage_priv ]" as String, result.CloudClusterPrivs as String)
+
+    def otherRole = "testOtherRole"
+    def testClusterB = "clusterB"
+
+    sql """
+        grant usage_priv on cluster ${testClusterB} to role "${otherRole}";
+    """
+
+    result = showRoles.call(otherRole)
+    assertEquals(result.CloudClusterPrivs as String, "$testClusterB: Cluster_Usage_priv " as String)
+    assertEquals(result.Users as String, "")
+
+    // add more roles to user1
+    sql """
+        GRANT '$otherRole' TO '$user1';  
+    """
+    result = showRoles.call(otherRole)
+    assertEquals(result.CloudClusterPrivs as String, "$testClusterB: Cluster_Usage_priv " as String)
+    matcher = result.Users =~ /.*${user1}.*/
+    assertTrue(matcher.matches())
+
+    result = sql_return_maparray """show grants for '${user1}'"""
+    commonAuth result, "'${user1}'@'%'", "Yes", "testOtherRole,testRole", "Select_priv Cluster_Usage_priv "
+    assertEquals("[$testClusterA: Cluster_Usage_priv ; $testClusterB: Cluster_Usage_priv ; $validCluster: Cluster_Usage_priv ]" as String,
+        result.CloudClusterPrivs as String) 
+
+    // revoke cluster usage_priv from role
+    sql """
+        revoke usage_priv on cluster '$testClusterA' from role "${roleName}";
+        """
+
+    result = showRoles.call(roleName)
+    assertTrue(fieldDisorder.call(result.CloudClusterPrivs as String,
+        "$testClusterA: ; $validCluster: Cluster_Usage_priv " as String, 
+        "$validCluster: Cluster_Usage_priv ; $testClusterA: " as String) as boolean)
+    matcher = result.Users =~ /.*${user1}.*/
+    assertTrue(matcher.matches())
+
+    result = sql_return_maparray """show grants for '${user1}'"""
+    commonAuth result, "'${user1}'@'%'", "Yes", "testOtherRole,testRole", "Select_priv Cluster_Usage_priv "
+    assertEquals("[$testClusterB: Cluster_Usage_priv ; $validCluster: Cluster_Usage_priv ]" as String,
+        result.CloudClusterPrivs as String) 
+    
+    // revoke otherRole from user1
+    sql """
+        REVOKE '$otherRole' FROM '${user1}'
+    """
+
+    result = showRoles.call(otherRole)
+    assertEquals(result.CloudClusterPrivs as String, "$testClusterB: Cluster_Usage_priv " as String)
+    assertEquals(result.Users as String, "")
+
+    result = sql_return_maparray """show grants for '${user1}'"""
+    commonAuth result, "'${user1}'@'%'", "Yes", "testRole", "Select_priv Cluster_Usage_priv "
+    assertEquals("[$validCluster: Cluster_Usage_priv ]" as String, result.CloudClusterPrivs as String) 
+
+    sql """
+        revoke usage_priv on cluster ${validCluster} from role "${roleName}";
+        """
+
+    result = showRoles.call(roleName)
+    assertTrue(fieldDisorder.call(result.CloudClusterPrivs as String,
+        "$testClusterA: ; $validCluster: " as String, 
+        "$validCluster: ; $testClusterA: " as String as String) as boolean)
+
+    // still can select, because have global * cluster
+    connect(user = "${user1}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+        sql """use @${validCluster}"""
+        def sqlRet = sql """SELECT * FROM ${db}.${tbl}"""
+        assertEquals(sqlRet[0][0] as int, 1)
+        assertEquals(sqlRet[0][1] as int, 10)
+    } 
+
+    result = sql_return_maparray """show grants for '${user1}'"""
+    commonAuth result, "'${user1}'@'%'", "Yes", "testRole", "Select_priv Cluster_Usage_priv "
+    assertNull(result.CloudClusterPrivs[0])
+
+    // revoke global * from role
+    sql """
+        revoke usage_priv on cluster * from role "${roleName}";
+        """
+
+    result = showRoles.call(roleName)
+    assertTrue(fieldDisorder.call(result.CloudClusterPrivs as String,
+        "$testClusterA: ; $validCluster: " as String, 
+        "$validCluster: ; $testClusterA: " as String as String) as boolean)
+    assertEquals(result.GlobalPrivs as String, "Select_priv  " as String)
+
+    result = sql_return_maparray """show grants for '${user1}'"""
+    commonAuth result, "'${user1}'@'%'", "Yes", "testRole", "Select_priv "
+
+    // can not use @cluster, because no cluster auth
+    connect(user = "${user1}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+        test {
+            sql """use @${validCluster}"""
+            exception "USAGE denied to user"
+        }
+    } 
+
+    sql """
+        drop user ${user1}
+        """
+
+    sql """
+        drop role ${roleName}
+        """
+
+    sql """
+        drop role ${otherRole}
+        """
+}
+
diff --git a/regression-test/suites/cloud/auth/test_grant_revoke_cluster_to_user.groovy b/regression-test/suites/cloud/auth/test_grant_revoke_cluster_to_user.groovy
new file mode 100644
index 00000000000..b26ccfbf984
--- /dev/null
+++ b/regression-test/suites/cloud/auth/test_grant_revoke_cluster_to_user.groovy
@@ -0,0 +1,308 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_grant_revoke_cluster_to_user", "cloud_auth") {
+    def isCloudMode = {
+        def ret = sql_return_maparray  """show backends"""
+        ret.Tag[0].contains("cloud_cluster_name")
+    }
+    def cloudMode = isCloudMode.call()
+    log.info("is cloud mode $cloudMode")
+    if (!cloudMode) {
+        return
+    }
+    def role = "admin"
+    def user1 = "regression_test_cloud_user1"
+    def user2 = "regression_test_cloud_user2"
+    def user3 = "regression_test_cloud_user3"
+    def tbl = "test_auth_tbl"
+
+    sql """drop user if exists ${user1}"""
+    sql """drop user if exists ${user2}"""
+    sql """drop user if exists ${user3}"""
+    sql """drop table if exists ${tbl}"""
+
+    def getCluster = { cluster ->
+        def result = sql " SHOW CLUSTERS; "
+        for (int i = 0; i < result.size(); i++) {
+            if (result[i][0] == cluster) {
+                return result[i]
+            }
+        }
+        return null
+    }
+
+    def commonAuth = { result, UserIdentity, Password, Roles, GlobalPrivs ->
+        assertEquals(UserIdentity as String, result.UserIdentity[0] as String)
+        assertEquals(Password as String, result.Password[0] as String)
+        assertEquals(Roles as String, result.Roles[0] as String)
+        assertEquals(GlobalPrivs as String, result.GlobalPrivs[0] as String)
+    }
+
+    def getProperty = { property, user ->
+        def result = null
+        if (user == "") {
+            result = sql_return_maparray """SHOW PROPERTY""" 
+        } else {
+            result = sql_return_maparray """SHOW PROPERTY FOR '${user}'""" 
+        }
+        result.find {
+            it.Key == property as String
+        }
+    }
+
+    def clusters = sql " SHOW CLUSTERS; "
+    assertTrue(!clusters.isEmpty())
+    def validCluster = clusters[0][0]
+
+    // 1. change user
+    // ${user1} admin role
+    sql """create user ${user1} identified by 'Cloud12345' default role 'admin'"""
+    result = sql_return_maparray """show grants for '${user1}'"""
+    commonAuth result, "'${user1}'@'%'" as String, "Yes", "admin", "Admin_priv "
+    assertNull(result.CloudClusterPrivs[0])
+
+    // ${user2} not admin role
+    sql """create user ${user2} identified by 'Cloud12345'"""
+    sql """GRANT USAGE_PRIV ON CLUSTER '${validCluster}' TO '${user2}'"""
+    // for use default_cluster:regression_test
+    sql """grant select_priv on *.*.* to ${user2}"""
+
+
+    sql """
+    CREATE TABLE ${tbl} (
+    `k1` int(11) NULL,
+    `k2` char(5) NULL
+    )
+    DUPLICATE KEY(`k1`, `k2`)
+    COMMENT 'OLAP'
+    DISTRIBUTED BY HASH(`k1`) BUCKETS 1
+    PROPERTIES (
+    "replication_num"="1"
+    );
+    """
+
+    sql """
+        insert into ${tbl} (k1, k2) values (1, "10");
+    """
+
+    sql """create user ${user3} identified by 'Cloud12345'"""
+    sql """GRANT SELECT_PRIV ON *.*.* TO '${user3}'@'%'"""
+    result = connect(user = "${user3}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+            sql """SHOW CLUSTERS"""
+    }
+    // not grant any cluster to user3
+    assertTrue(result.isEmpty())
+    def db = context.dbName
+
+    connect(user = "${user3}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+        test {
+            sql """select * from ${db}.${tbl}"""
+            exception "or you may not have permission to access the current cluster"
+        }
+    }
+
+    // 2. grant cluster
+    def cluster1 = "clusterA"
+    def result
+
+    sql "sync"
+
+    // admin role user can grant cluster to use
+    result = connect(user = "${user1}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+            sql """GRANT USAGE_PRIV ON CLUSTER '${cluster1}' TO '${user1}'"""
+    }
+
+    // case run user(default root), and show grant again, should be same result
+    result = sql_return_maparray """show grants for '${user1}'"""
+    commonAuth result, "'${user1}'@'%'" as String, "Yes", "admin", "Admin_priv "
+    assertEquals(result.CloudClusterPrivs[0] as String, "${cluster1}: Cluster_Usage_priv " as String)
+
+    sql """GRANT USAGE_PRIV ON CLUSTER '${cluster1}' TO '${user1}'"""
+    result = sql_return_maparray """show grants for '${user1}'"""
+    commonAuth result, "'${user1}'@'%'" as String, "Yes", "admin", "Admin_priv "
+    assertEquals(result.CloudClusterPrivs[0] as String, "${cluster1}: Cluster_Usage_priv " as String)
+
+    connect(user = "${user1}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+        test {
+            sql """use @${cluster1}"""
+            exception "Cluster ${cluster1} not exist"
+        }
+        result = sql_return_maparray """show grants for '${user1}'"""
+        commonAuth result, "'${user1}'@'%'", "Yes", "admin", "Admin_priv "
+        assertEquals(result.CloudClusterPrivs[0] as String, "${cluster1}: Cluster_Usage_priv " as String)
+    }
+
+
+    try {
+        result = connect(user = "${user2}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+             sql """GRANT USAGE_PRIV ON CLUSTER '${cluster1}' TO '${user1}'"""
+        }
+    } catch (Exception e) {
+        assertTrue(e.getMessage().contains("Access denied; you need (at least one of) the GRANT/ROVOKE privilege(s) for this operation"), e.getMessage())
+    }
+
+    // default cloud cluster
+    sql """SET PROPERTY FOR '${user1}' 'default_cloud_cluster' = '${validCluster}'"""
+    sql """SET PROPERTY FOR '${user2}' 'default_cloud_cluster' = '${validCluster}'"""
+    def show_cluster_1 = getCluster(validCluster)
+
+    assertTrue(show_cluster_1[2].contains(user2), "Expect contain users ${user2}")
+
+    result = getProperty("default_cloud_cluster", "${user1}")
+    assertEquals(result.Value as String, "${validCluster}" as String)
+
+    connect(user = "${user2}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+        result = sql """use @${validCluster}"""
+        assertEquals(result[0][0], 0)
+        result = getProperty("default_cloud_cluster", "")
+        assertEquals(result.Value as String, "${validCluster}" as String) 
+    }
+        // set default_cloud_cluster to ''
+    sql """SET PROPERTY FOR '${user2}' 'default_cloud_cluster' = ''"""
+    connect(user = "${user2}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+        result = getProperty("default_cloud_cluster", "")
+        assertEquals(result.Value as String, "" as String) 
+    } 
+
+    sql """SET PROPERTY FOR '${user2}' 'default_cloud_cluster' = '${validCluster}'"""
+    result = sql """REVOKE USAGE_PRIV ON CLUSTER '${validCluster}' FROM '${user2}'"""
+    assertEquals(result[0][0], 0)
+    connect(user = "${user2}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+        test {
+            sql """use @${cluster1}"""
+            exception "USAGE denied to user"
+        }
+    }
+
+    connect(user = "${user2}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+        test {
+            sql """use @${validCluster}"""
+            exception "USAGE denied to user"
+        }
+    }
+
+    // grant GRANT_PRIV to general user, he can grant cluster to other user.
+    sql """grant GRANT_PRIV on *.*.* to ${user2}"""
+
+    sql "sync"
+
+    test {
+        sql """REVOKE USAGE_PRIV ON CLUSTER '${cluster1}' FROM '${user2}'"""
+        exception "${cluster1} does not exist in role"
+    }
+
+    result = connect(user = "${user2}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+            sql """GRANT USAGE_PRIV ON CLUSTER '${cluster1}' TO '${user2}'"""
+    }
+
+    sql """GRANT USAGE_PRIV ON CLUSTER '${validCluster}' TO '${user2}'"""
+    show_cluster_2 = connect(user = "${user2}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+            getCluster(validCluster)
+    }
+
+    assertTrue(show_cluster_2[2].equals(user2), "Expect just only have user ${user2}")
+
+    result = connect(user = "${user2}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+            sql """USE @${validCluster}"""
+    }
+    assertEquals(result[0][0], 0)
+
+    sql """REVOKE USAGE_PRIV ON CLUSTER '${validCluster}' FROM '${user2}'"""
+
+    connect(user = "${user2}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+        test {
+            sql """use @${validCluster}"""
+            exception "USAGE denied to user"
+        }
+        result = sql_return_maparray """show grants for '${user2}'"""
+        commonAuth result, "'${user2}'@'%'" as String, "Yes", "", "Grant_priv Select_priv "
+        assertEquals(result.CloudClusterPrivs[0] as String, "${cluster1}: Cluster_Usage_priv " as String)
+
+        test {
+            sql """REVOKE USAGE_PRIV ON CLUSTER 'NotExistCluster' FROM '${user2}'"""
+            exception "NotExistCluster does not exist in role"
+        }
+    }
+    
+    sql """REVOKE USAGE_PRIV ON CLUSTER '${validCluster}' FROM '${user2}'"""
+    result = sql_return_maparray """show grants for '${user2}'"""
+    commonAuth result, "'${user2}'@'%'" as String, "Yes", "", "Grant_priv Select_priv "
+    assertEquals(result.CloudClusterPrivs[0] as String, "${cluster1}: Cluster_Usage_priv " as String)
+
+    sql "sync"
+    // 3. revoke cluster
+    // admin role user can revoke cluster
+    result = connect(user = "${user1}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+            sql """REVOKE USAGE_PRIV ON CLUSTER '${cluster1}' FROM '${user1}'"""
+    }
+
+    // revoke GRANT_PRIV from general user, he can not revoke cluster to other user.
+    sql """revoke GRANT_PRIV on *.*.* from ${user2}"""
+
+    sql "sync"
+    
+    // general user can't revoke cluster
+    try {
+        result = connect(user = "${user2}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+             sql """REVOKE USAGE_PRIV ON CLUSTER '${cluster1}' FROM '${user2}'"""
+        }
+    } catch (Exception e) {
+        assertTrue(e.getMessage().contains("Access denied; you need (at least one of) the GRANT/ROVOKE privilege(s) for this operation"), e.getMessage())
+    }
+
+    result = sql_return_maparray """show grants for '${user1}'"""
+    commonAuth result, "'${user1}'@'%'" as String, "Yes", "admin", "Admin_priv "
+    assertNull(result.CloudClusterPrivs[0])
+
+    result = sql_return_maparray """show grants for '${user2}'"""
+    commonAuth result, "'${user2}'@'%'" as String, "Yes", "", "Select_priv "
+    assertEquals(result.CloudClusterPrivs[0] as String, "${cluster1}: Cluster_Usage_priv " as String)
+
+    // revoke user1 admin role
+    sql """REVOKE 'admin' FROM ${user1}"""
+    result = sql_return_maparray """show grants for '${user1}'"""
+    assertEquals("'${user1}'@'%'" as String, result.UserIdentity[0] as String)
+    assertEquals("", result.Roles[0])
+    assertNull(result.GlobalPrivs[0])
+    assertNull(result.CloudClusterPrivs[0])
+
+    // user1 no admin auth, so failed to set other default cloud cluster
+    try {
+        result = connect(user = "${user1}", password = 'Cloud12345', url = context.config.jdbcUrl) {
+            sql """SET PROPERTY FOR '${user2}' 'default_cloud_cluster' = '${validCluster}'""" 
+        }
+    } catch (Exception e) {
+        assertTrue(e.getMessage().contains("Access denied for user"), e.getMessage())
+    }
+
+    sql """drop user if exists ${user1}"""
+        // grant not exists user
+    result = sql """GRANT USAGE_PRIV ON CLUSTER '${cluster1}' TO 'NotExitUser'"""
+    assertEquals(result[0][0], 0)
+
+    // drop user and grant he cluster priv
+    result = sql """GRANT USAGE_PRIV ON CLUSTER '${cluster1}' TO '${user1}'"""
+    assertEquals(result[0][0], 0)
+    result = sql """REVOKE USAGE_PRIV ON CLUSTER '${cluster1}' FROM '${user1}'"""
+    assertEquals(result[0][0], 0)
+    // general user can't grant cluster to use
+    sql """drop user if exists ${user2}"""
+    sql """drop user if exists ${user3}"""
+}
+
+


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org