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