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

deardeng 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 788baa53665 [feature](compute group) Support show compute groups in 
non cloud mode (#66697)
788baa53665 is described below

commit 788baa53665c96312a10b2d3830094b34d21c5c7
Author: deardeng <[email protected]>
AuthorDate: Wed Aug 19 14:22:23 2026 +0800

    [feature](compute group) Support show compute groups in non cloud mode 
(#66697)
    
    SHOW CLUSTERS / SHOW COMPUTE GROUPS were rejected with
    ERR_NOT_CLOUD_MODE in non cloud mode. In non cloud mode a resource group
    (the backend location tag) is the counterpart of a cloud compute group,
    so show it instead:
    
    - only Name/BackendNum (cluster/backend_num for SHOW CLUSTERS) are
    shown, IsCurrent/Users/SubComputeGroups/Policy/Properties are cloud
    only.
    - a user only sees the resource groups it is allowed to use, which is
    the compute group resolved from resource_tags.location, the same
    visibility the query engine uses to pick backends. This replaces the
    global ADMIN check, and matches cloud mode where clusters are filtered
    by usage priv.
    
    <img width="454" height="538" alt="image"
    
src="https://github.com/user-attachments/assets/3374efd8-0a00-43de-94dc-043d73c21ed1";
    />
---
 .../trees/plans/commands/ShowClustersCommand.java  | 42 ++++++++++-------
 .../trees/plans/commands/ShowComputeGroupTest.java | 55 ++++++++++++++++++----
 .../show_p0/test_show_compute_groups_docker.out    |  9 ++++
 .../suites/show_p0/test_show_compute_groups.groovy | 53 +++++++++++++++++++++
 .../show_p0/test_show_compute_groups_docker.groovy | 35 ++++++++++++++
 5 files changed, 167 insertions(+), 27 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowClustersCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowClustersCommand.java
index c774849ed48..92bf2c30b85 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowClustersCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowClustersCommand.java
@@ -25,10 +25,7 @@ import org.apache.doris.catalog.ScalarType;
 import org.apache.doris.cloud.catalog.CloudComputeGroupMeta;
 import org.apache.doris.cloud.qe.ComputeGroupException;
 import org.apache.doris.cloud.system.CloudSystemInfoService;
-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.Auth;
 import org.apache.doris.mysql.privilege.PrivBitSet;
 import org.apache.doris.mysql.privilege.PrivPredicate;
@@ -39,6 +36,7 @@ import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.qe.ShowResultSet;
 import org.apache.doris.qe.ShowResultSetMetaData;
 import org.apache.doris.qe.StmtExecutor;
+import org.apache.doris.resource.computegroup.ComputeGroup;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
@@ -49,7 +47,9 @@ import org.apache.logging.log4j.Logger;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.stream.Collectors;
 
 /**
@@ -64,6 +64,11 @@ public class ShowClustersCommand extends ShowCommand {
     public static final ImmutableList<String> COMPUTE_GROUP_TITLE_NAMES = new 
ImmutableList.Builder<String>()
             .add("Name").add("IsCurrent").add("Users").add("BackendNum")
             .add("SubComputeGroups").add("Policy").add("Properties").build();
+    // non cloud mode, a resource group(backend location tag) is the 
counterpart of a cloud compute group
+    public static final ImmutableList<String> CLUSTER_TITLE_NAMES_NON_CLOUD = 
new ImmutableList.Builder<String>()
+            .add("cluster").add("backend_num").build();
+    public static final ImmutableList<String> 
COMPUTE_GROUP_TITLE_NAMES_NON_CLOUD = new ImmutableList.Builder<String>()
+            .add("Name").add("BackendNum").build();
 
     private static final Logger LOG = 
LogManager.getLogger(ShowClustersCommand.class);
     private final boolean isComputeGroup;
@@ -73,22 +78,23 @@ public class ShowClustersCommand extends ShowCommand {
         this.isComputeGroup = isComputeGroup;
     }
 
-    private void validate(ConnectContext ctx) throws AnalysisException {
-        if (Config.isNotCloudMode()) {
-            // just user admin
-            if 
(!Env.getCurrentEnv().getAccessManager().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 ShowResultSet doRun(ConnectContext ctx, StmtExecutor executor) 
throws Exception {
-        validate(ctx);
         final List<List<String>> rows = Lists.newArrayList();
-        if (!Config.isCloudMode()) {
-            ErrorReport.reportAnalysisException(ErrorCode.ERR_NOT_CLOUD_MODE);
+        if (Config.isNotCloudMode()) {
+            // resource group is the compute group of non cloud mode, a user 
only sees the resource groups
+            // it is allowed to use, same as the cloud mode which filters 
clusters by usage priv.
+            ComputeGroup userComputeGroup = ctx.getComputeGroup();
+            if (ComputeGroup.INVALID_COMPUTE_GROUP == userComputeGroup) {
+                return new ShowResultSet(getMetaData(), rows);
+            }
+            Map<String, Long> backendNumByGroup = 
Env.getCurrentSystemInfo().getAllClusterBackends(false).stream()
+                    .map(be -> be.getLocationTag().value)
+                    .filter(userComputeGroup::containsBackend)
+                    .collect(Collectors.groupingBy(name -> name, TreeMap::new, 
Collectors.counting()));
+            for (Map.Entry<String, Long> entry : backendNumByGroup.entrySet()) 
{
+                rows.add(Lists.newArrayList(entry.getKey(), 
String.valueOf(entry.getValue())));
+            }
             return new ShowResultSet(getMetaData(), rows);
         }
 
@@ -181,9 +187,9 @@ public class ShowClustersCommand extends ShowCommand {
 
         ImmutableList<String> titleNames = null;
         if (isComputeGroup) {
-            titleNames = COMPUTE_GROUP_TITLE_NAMES;
+            titleNames = Config.isNotCloudMode() ? 
COMPUTE_GROUP_TITLE_NAMES_NON_CLOUD : COMPUTE_GROUP_TITLE_NAMES;
         } else {
-            titleNames = CLUSTER_TITLE_NAMES;
+            titleNames = Config.isNotCloudMode() ? 
CLUSTER_TITLE_NAMES_NON_CLOUD : CLUSTER_TITLE_NAMES;
         }
 
         for (String title : titleNames) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowComputeGroupTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowComputeGroupTest.java
index 06588301872..cab5353856c 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowComputeGroupTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowComputeGroupTest.java
@@ -18,11 +18,14 @@
 package org.apache.doris.nereids.trees.plans.commands;
 
 import org.apache.doris.catalog.Column;
-import org.apache.doris.common.AnalysisException;
+import org.apache.doris.catalog.Env;
 import org.apache.doris.common.Config;
 import org.apache.doris.qe.ShowResultSetMetaData;
+import org.apache.doris.resource.Tag;
+import org.apache.doris.system.Backend;
 import org.apache.doris.utframe.TestWithFeService;
 
+import com.google.common.collect.Lists;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -38,6 +41,7 @@ public class ShowComputeGroupTest extends TestWithFeService {
     @Test
     public void testShowComputeGroupsInCloudMode() throws Exception {
         Config.deploy_mode = "cloud";
+        Config.cloud_unique_id = "cloud_unique_id";
         ShowClustersCommand command = new ShowClustersCommand(true);
         ShowResultSetMetaData metaData = command.getMetaData();
         Assertions.assertNotNull(metaData);
@@ -56,15 +60,47 @@ public class ShowComputeGroupTest extends TestWithFeService 
{
 
     @Test
     public void testShowComputeGroupsInNonCloudMode() throws Exception {
-        Config.deploy_mode = "not-cloud";
+        Config.deploy_mode = "";
+        Config.cloud_unique_id = "";
+        Tag groupA = Tag.create(Tag.TYPE_LOCATION, "group_a");
+        Backend groupABackend1 = addNewBackend();
+        Backend groupABackend2 = addNewBackend();
+        Backend groupBBackend = addNewBackend();
+        groupABackend1.setTagMap(groupA.toMap());
+        groupABackend2.setTagMap(groupA.toMap());
+        groupBBackend.setTagMap(Tag.create(Tag.TYPE_LOCATION, 
"group_b").toMap());
+
         ShowClustersCommand command = new ShowClustersCommand(true);
-        Assertions.assertThrows(AnalysisException.class, () -> {
-            command.doRun(connectContext, null);
-        });
+        List<String> columnNames = command.getMetaData().getColumns().stream()
+                .map(Column::getName).collect(Collectors.toList());
+        Assertions.assertEquals(Lists.newArrayList("Name", "BackendNum"), 
columnNames);
+        List<List<String>> rows = command.doRun(connectContext, 
null).getResultRows();
+        List<List<String>> expectedRows = Lists.newArrayList(
+                Lists.newArrayList(Tag.VALUE_DEFAULT_TAG, "1"),
+                Lists.newArrayList("group_a", "2"),
+                Lists.newArrayList("group_b", "1"));
+        Assertions.assertEquals(expectedRows, rows);
+
+        // a user restricted by resource_tags.location only sees the resource 
groups it can use,
+        // this is the compute group bound to the session when the user logs 
in.
+        executeSql("CREATE USER show_cg_user IDENTIFIED BY '12345'");
+        try {
+            executeSql("SET PROPERTY FOR 'show_cg_user' 
'resource_tags.location' = 'group_a'");
+            
connectContext.setComputeGroup(Env.getCurrentEnv().getAuth().getComputeGroup("show_cg_user"));
+            Assertions.assertEquals(expectedRows.subList(1, 2), 
command.doRun(connectContext, null).getResultRows());
+
+            executeSql("SET PROPERTY FOR 'show_cg_user' 
'resource_tags.location' = 'no_such_resource_group'");
+            
connectContext.setComputeGroup(Env.getCurrentEnv().getAuth().getComputeGroup("show_cg_user"));
+            Assertions.assertTrue(command.doRun(connectContext, 
null).getResultRows().isEmpty());
+        } finally {
+            connectContext.setComputeGroup(null);
+        }
     }
 
     @Test
     public void testShowClustersInCloudMode() throws Exception {
+        Config.deploy_mode = "cloud";
+        Config.cloud_unique_id = "cloud_unique_id";
         ShowClustersCommand command = new ShowClustersCommand(false);
         ShowResultSetMetaData metaData = command.getMetaData();
         Assertions.assertNotNull(metaData);
@@ -82,10 +118,11 @@ public class ShowComputeGroupTest extends 
TestWithFeService {
 
     @Test
     public void testShowClustersInNonCloudMode() throws Exception {
-        Config.deploy_mode = "not-cloud";
+        Config.deploy_mode = "";
+        Config.cloud_unique_id = "";
         ShowClustersCommand command = new ShowClustersCommand(false);
-        Assertions.assertThrows(AnalysisException.class, () -> {
-            command.doRun(connectContext, null);
-        });
+        List<String> columnNames = command.getMetaData().getColumns().stream()
+                .map(Column::getName).collect(Collectors.toList());
+        Assertions.assertEquals(Lists.newArrayList("cluster", "backend_num"), 
columnNames);
     }
 }
diff --git a/regression-test/data/show_p0/test_show_compute_groups_docker.out 
b/regression-test/data/show_p0/test_show_compute_groups_docker.out
new file mode 100644
index 00000000000..9543f08d0d6
--- /dev/null
+++ b/regression-test/data/show_p0/test_show_compute_groups_docker.out
@@ -0,0 +1,9 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !show_compute_groups --
+group_a        2
+group_b        1
+
+-- !show_clusters --
+group_a        2
+group_b        1
+
diff --git a/regression-test/suites/show_p0/test_show_compute_groups.groovy 
b/regression-test/suites/show_p0/test_show_compute_groups.groovy
new file mode 100644
index 00000000000..1ff55109a72
--- /dev/null
+++ b/regression-test/suites/show_p0/test_show_compute_groups.groovy
@@ -0,0 +1,53 @@
+// 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_show_compute_groups") {
+    if (isCloudMode()) {
+        return
+    }
+
+    // in non cloud mode, a compute group is a resource group, aka the backend 
location tag
+    def groups = sql "SHOW COMPUTE GROUPS"
+    assertTrue(!groups.isEmpty())
+    groups.each { row ->
+        assertEquals(2, row.size())
+        assertTrue(Integer.parseInt(row[1].toString()) > 0)
+    }
+    assertEquals(groups, sql("SHOW CLUSTERS"))
+
+    // a common user only sees the resource groups it is allowed to use
+    String user = 'test_show_compute_groups_user'
+    String pwd = 'C123_567p'
+    try_sql("DROP USER ${user}")
+    sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'"""
+    try {
+        def boundGroup = groups[0]
+        def noDbJdbcUrl = 
context.config.jdbcUrl.replaceFirst(/(jdbc:mysql:\/\/[^\/]+\/)[^?]*/, '$1')
+        sql """SET PROPERTY FOR '${user}' 'resource_tags.location' = 
'${boundGroup[0]}'"""
+        connect(user, "${pwd}", noDbJdbcUrl) {
+            assertEquals([boundGroup], sql("SHOW COMPUTE GROUPS"))
+        }
+
+        // no such resource group, the user sees nothing instead of an error
+        sql """SET PROPERTY FOR '${user}' 'resource_tags.location' = 
'no_such_resource_group'"""
+        connect(user, "${pwd}", noDbJdbcUrl) {
+            assertTrue(sql("SHOW COMPUTE GROUPS").isEmpty())
+        }
+    } finally {
+        try_sql("DROP USER ${user}")
+    }
+}
diff --git 
a/regression-test/suites/show_p0/test_show_compute_groups_docker.groovy 
b/regression-test/suites/show_p0/test_show_compute_groups_docker.groovy
new file mode 100644
index 00000000000..7fc280bc4fd
--- /dev/null
+++ b/regression-test/suites/show_p0/test_show_compute_groups_docker.groovy
@@ -0,0 +1,35 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import org.apache.doris.regression.suite.ClusterOptions
+
+suite("test_show_compute_groups_docker", "docker") {
+    def options = new ClusterOptions()
+    options.cloudMode = false
+    options.feNum = 1
+    options.beNum = 3
+
+    docker(options) {
+        def backends = sql_return_maparray "SHOW BACKENDS"
+        sql """ALTER SYSTEM MODIFY BACKEND "${backends[0].BackendId}" SET 
("tag.location" = "group_a")"""
+        sql """ALTER SYSTEM MODIFY BACKEND "${backends[1].BackendId}" SET 
("tag.location" = "group_a")"""
+        sql """ALTER SYSTEM MODIFY BACKEND "${backends[2].BackendId}" SET 
("tag.location" = "group_b")"""
+
+        order_qt_show_compute_groups "SHOW COMPUTE GROUPS"
+        order_qt_show_clusters "SHOW CLUSTERS"
+    }
+}


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

Reply via email to