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

leborchuk pushed a commit to branch REL_2_STABLE
in repository https://gitbox.apache.org/repos/asf/cloudberry.git


The following commit(s) were added to refs/heads/REL_2_STABLE by this push:
     new ab2c3f2e8a8 Let mdb_admin manage resource groups (#1804)
ab2c3f2e8a8 is described below

commit ab2c3f2e8a8390d9f8f9d228c330e52b94d9537b
Author: Alena Rybakina <[email protected]>
AuthorDate: Thu Aug 27 14:10:44 2026 +0300

    Let mdb_admin manage resource groups (#1804)
    
    Add gp_toolkit.pg_create_mdb_admin_role() (gp_toolkit 1.7) to provision the
    role with its fixed OID.
    
    Allow members of mdb_admin — not just superusers — to CREATE/ALTER/DROP
    resource groups and run pg_resgroup_move_query(), so a cloud admin can tune
    their own CPU/memory limits.
    
    Based on pg-sharding/cpg 7b8c912. Some tests are adapted from
    open-gpdb/gpdb 3ac99962ad2.
    
    Co-authored-by: Andrey Borodin <[email protected]>
---
 gpcontrib/gp_toolkit/Makefile                      |   8 +-
 .../gp_toolkit/expected/gp_toolkit_mdb_admin.out   |  46 ++++++++
 gpcontrib/gp_toolkit/gp_toolkit--1.6--1.7.sql      |  21 ++++
 gpcontrib/gp_toolkit/gp_toolkit.control            |   2 +-
 gpcontrib/gp_toolkit/mdb_admin.c                   |  86 ++++++++++++++
 gpcontrib/gp_toolkit/sql/gp_toolkit_mdb_admin.sql  |  37 ++++++
 src/backend/catalog/oid_dispatch.c                 |  50 +++++++-
 src/backend/commands/resgroupcmds.c                |  51 +++++---
 src/backend/utils/resgroup/resgroup_helper.c       |   5 +-
 src/include/access/transam.h                       |  10 ++
 src/include/catalog/oid_dispatch.h                 |   7 ++
 src/include/utils/acl.h                            |   8 ++
 .../expected/resgroup/resgroup_mdb_admin.out       | 128 +++++++++++++++++++++
 .../isolation2/isolation2_resgroup_v1_schedule     |   3 +
 .../isolation2/isolation2_resgroup_v2_schedule     |   3 +
 .../isolation2/sql/resgroup/resgroup_mdb_admin.sql |  91 +++++++++++++++
 16 files changed, 528 insertions(+), 28 deletions(-)

diff --git a/gpcontrib/gp_toolkit/Makefile b/gpcontrib/gp_toolkit/Makefile
index 82fc7f7dee9..edb63558593 100644
--- a/gpcontrib/gp_toolkit/Makefile
+++ b/gpcontrib/gp_toolkit/Makefile
@@ -1,15 +1,15 @@
 EXTENSION = gp_toolkit
 DATA = gp_toolkit--1.1--1.2.sql gp_toolkit--1.0--1.1.sql gp_toolkit--1.0.sql \
                gp_toolkit--1.2--1.3.sql gp_toolkit--1.3.sql 
gp_toolkit--1.3--1.4.sql \
-               gp_toolkit--1.4--1.5.sql gp_toolkit--1.5--1.6.sql
+               gp_toolkit--1.4--1.5.sql gp_toolkit--1.5--1.6.sql 
gp_toolkit--1.6--1.7.sql
 MODULE_big = gp_toolkit
 ifeq ($(shell uname -s), Linux)
-OBJS = resgroup.o gp_partition_maint.o
+OBJS = resgroup.o gp_partition_maint.o mdb_admin.o
 else
-OBJS = resgroup-dummy.o gp_partition_maint.o
+OBJS = resgroup-dummy.o gp_partition_maint.o mdb_admin.o
 endif
 
-REGRESS = resource_manager_restore_to_none gp_toolkit 
resource_manager_switch_to_queue gp_toolkit_resqueue gp_toolkit_ao_funcs 
gp_partition_maint
+REGRESS = resource_manager_restore_to_none gp_toolkit gp_toolkit_mdb_admin 
resource_manager_switch_to_queue gp_toolkit_resqueue gp_toolkit_ao_funcs 
gp_partition_maint
 EXTRA_REGRESS_OPTS = --init-file=$(top_builddir)/src/test/regress/init_file
 
 ifdef USE_PGXS
diff --git a/gpcontrib/gp_toolkit/expected/gp_toolkit_mdb_admin.out 
b/gpcontrib/gp_toolkit/expected/gp_toolkit_mdb_admin.out
new file mode 100644
index 00000000000..aefc4a9b3e8
--- /dev/null
+++ b/gpcontrib/gp_toolkit/expected/gp_toolkit_mdb_admin.out
@@ -0,0 +1,46 @@
+-- Tests for gp_toolkit.pg_create_mdb_admin_role(): creation of the
+-- fixed-OID mdb_admin role and the resource-group permission gate it
+-- enables.  gp_toolkit is installed in template1 by gpinitsystem, so the
+-- extension is already present in this database.
+-- ---------------------------------------------------------------------
+-- pg_create_mdb_admin_role() creates the mdb_admin role with its fixed OID.
+-- ---------------------------------------------------------------------
+SELECT gp_toolkit.pg_create_mdb_admin_role() AS mdb_admin_oid;
+ mdb_admin_oid 
+---------------
+          8067
+(1 row)
+
+-- The role exists with the fixed OID and is a non-login, non-superuser,
+-- connection-limited role.
+SELECT oid = 8067 AS has_fixed_oid, rolcanlogin, rolsuper,
+       rolcreaterole, rolcreatedb, rolconnlimit
+  FROM pg_authid WHERE rolname = 'mdb_admin';
+ has_fixed_oid | rolcanlogin | rolsuper | rolcreaterole | rolcreatedb | 
rolconnlimit 
+---------------+-------------+----------+---------------+-------------+--------------
+ t             | f           | f        | f             | f           |        
    0
+(1 row)
+
+-- Creating it a second time is rejected.
+SELECT gp_toolkit.pg_create_mdb_admin_role();
+ERROR:  role with OID 8067 already exists
+-- ---------------------------------------------------------------------
+-- Resource-group permission gate: a role that is not a member of mdb_admin
+-- is rejected on every entry point.  These checks run before the "resource
+-- group is enabled" check, so they are deterministic regardless of the
+-- resource manager in use.
+-- ---------------------------------------------------------------------
+CREATE ROLE regress_rg_noadmin;
+SET ROLE regress_rg_noadmin;
+CREATE RESOURCE GROUP regress_rg_x WITH (concurrency=1, cpu_max_percent=5);
+ERROR:  must be mdb_admin to create resource groups
+ALTER RESOURCE GROUP regress_rg_x SET cpu_max_percent 6;
+ERROR:  must be mdb_admin to alter resource groups
+DROP RESOURCE GROUP regress_rg_x;
+ERROR:  must be mdb_admin to drop resource groups
+RESET ROLE;
+DROP ROLE regress_rg_noadmin;
+-- ---------------------------------------------------------------------
+-- Cleanup.
+-- ---------------------------------------------------------------------
+DROP ROLE mdb_admin;
diff --git a/gpcontrib/gp_toolkit/gp_toolkit--1.6--1.7.sql 
b/gpcontrib/gp_toolkit/gp_toolkit--1.6--1.7.sql
new file mode 100644
index 00000000000..32af2e25abe
--- /dev/null
+++ b/gpcontrib/gp_toolkit/gp_toolkit--1.6--1.7.sql
@@ -0,0 +1,21 @@
+/* gpcontrib/gp_toolkit/gp_toolkit--1.6--1.7.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION gp_toolkit UPDATE TO '1.7'" to load this file. \quit
+
+-- Create the mdb_admin privilege role with its fixed OID (8067).
+--
+-- The server lets members of mdb_admin CREATE/ALTER/DROP resource groups and
+-- run pg_resgroup_move_query() without superuser.  The role is identified by
+-- a fixed, well-known OID rather than by name, so it must be created through
+-- this function (a plain CREATE ROLE would assign an ordinary OID and the
+-- permission checks would not recognise its members).  The OID assignment is
+-- dispatched to the segments, so the role has the same OID cluster-wide.
+--
+-- Typical usage (once per cluster, as superuser):
+--   SELECT gp_toolkit.pg_create_mdb_admin_role();
+--   GRANT mdb_admin TO cloud_admin;
+CREATE FUNCTION gp_toolkit.pg_create_mdb_admin_role()
+RETURNS OID
+AS 'gp_toolkit.so', 'pg_create_mdb_admin_role'
+LANGUAGE C STRICT;
diff --git a/gpcontrib/gp_toolkit/gp_toolkit.control 
b/gpcontrib/gp_toolkit/gp_toolkit.control
index 0d9b47e7e6d..c8c0a4bacad 100644
--- a/gpcontrib/gp_toolkit/gp_toolkit.control
+++ b/gpcontrib/gp_toolkit/gp_toolkit.control
@@ -1,5 +1,5 @@
 # gp_toolkit extension
 
 comment = 'various GPDB administrative views/functions'
-default_version = '1.6'
+default_version = '1.7'
 schema = gp_toolkit
diff --git a/gpcontrib/gp_toolkit/mdb_admin.c b/gpcontrib/gp_toolkit/mdb_admin.c
new file mode 100644
index 00000000000..6ffc1eb0ad7
--- /dev/null
+++ b/gpcontrib/gp_toolkit/mdb_admin.c
@@ -0,0 +1,86 @@
+/*-------------------------------------------------------------------------
+ *
+ * mdb_admin.c
+ *       Provisioning of the mdb_admin privilege role
+ *
+ *       gpcontrib/gp_toolkit/mdb_admin.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/oid_dispatch.h"
+#include "catalog/pg_authid.h"
+#include "commands/user.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/parsenodes.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/syscache.h"
+
+/* Name of the mdb_admin role; its OID is MDB_ADMIN_ROLEID (see acl.h). */
+#define MDB_ADMIN_ROLE_NAME    "mdb_admin"
+
+PG_FUNCTION_INFO_V1(pg_create_mdb_admin_role);
+
+/*
+ * Create the mdb_admin role with its fixed OID (MDB_ADMIN_ROLEID, 8067).
+ *
+ * The core privilege checks identify mdb_admin by this fixed OID (see acl.c
+ * and resgroupcmds.c), so the role must always be created with it.  On a
+ * Cloudberry cluster the OID is dispatched to the segments so the role ends
+ * up with the same OID everywhere.  Returns the new role's OID.
+ */
+Datum
+pg_create_mdb_admin_role(PG_FUNCTION_ARGS)
+{
+       CreateRoleStmt stmt;
+       List       *options = NIL;
+       Oid                     roleid;
+
+       /*
+        * Only a superuser may establish the mdb_admin privilege role.  
Otherwise
+        * a CREATEROLE user could drop mdb_admin and re-create it (CreateRole 
only
+        * requires CREATEROLE), taking ownership of the fixed-OID role and
+        * granting the capability to itself.
+        */
+       if (!superuser())
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                errmsg("must be superuser to create the 
mdb_admin role")));
+
+       /* Check if a role with the fixed OID already exists. */
+       if (SearchSysCacheExists1(AUTHOID, ObjectIdGetDatum(MDB_ADMIN_ROLEID)))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                errmsg("role with OID %u already exists", 
MDB_ADMIN_ROLEID)));
+
+       /* Check if a role named "mdb_admin" already exists. */
+       if (SearchSysCacheExists1(AUTHNAME, 
CStringGetDatum(MDB_ADMIN_ROLE_NAME)))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DUPLICATE_OBJECT),
+                                errmsg("role \"%s\" already exists", 
MDB_ADMIN_ROLE_NAME)));
+
+       /* Build options for CreateRole: connection limit = 0. */
+       options = list_make1(makeDefElem("connectionlimit",
+                                                                        (Node 
*) makeInteger(0), -1));
+
+       /* Prepare the CreateRoleStmt. */
+       memset(&stmt, 0, sizeof(stmt));
+       stmt.type = T_CreateRoleStmt;
+       stmt.stmt_type = ROLESTMT_ROLE;
+       stmt.role = MDB_ADMIN_ROLE_NAME;
+       stmt.options = options;
+
+       /*
+        * Request the fixed OID for the role.  GetNewOidForAuthId() consumes 
and
+        * clears this override.
+        */
+       next_aux_pg_authid_oid = MDB_ADMIN_ROLEID;
+
+       roleid = CreateRole(NULL, &stmt);
+
+       PG_RETURN_OID(roleid);
+}
diff --git a/gpcontrib/gp_toolkit/sql/gp_toolkit_mdb_admin.sql 
b/gpcontrib/gp_toolkit/sql/gp_toolkit_mdb_admin.sql
new file mode 100644
index 00000000000..6643b1c218c
--- /dev/null
+++ b/gpcontrib/gp_toolkit/sql/gp_toolkit_mdb_admin.sql
@@ -0,0 +1,37 @@
+-- Tests for gp_toolkit.pg_create_mdb_admin_role(): creation of the
+-- fixed-OID mdb_admin role and the resource-group permission gate it
+-- enables.  gp_toolkit is installed in template1 by gpinitsystem, so the
+-- extension is already present in this database.
+
+-- ---------------------------------------------------------------------
+-- pg_create_mdb_admin_role() creates the mdb_admin role with its fixed OID.
+-- ---------------------------------------------------------------------
+SELECT gp_toolkit.pg_create_mdb_admin_role() AS mdb_admin_oid;
+
+-- The role exists with the fixed OID and is a non-login, non-superuser,
+-- connection-limited role.
+SELECT oid = 8067 AS has_fixed_oid, rolcanlogin, rolsuper,
+       rolcreaterole, rolcreatedb, rolconnlimit
+  FROM pg_authid WHERE rolname = 'mdb_admin';
+
+-- Creating it a second time is rejected.
+SELECT gp_toolkit.pg_create_mdb_admin_role();
+
+-- ---------------------------------------------------------------------
+-- Resource-group permission gate: a role that is not a member of mdb_admin
+-- is rejected on every entry point.  These checks run before the "resource
+-- group is enabled" check, so they are deterministic regardless of the
+-- resource manager in use.
+-- ---------------------------------------------------------------------
+CREATE ROLE regress_rg_noadmin;
+SET ROLE regress_rg_noadmin;
+CREATE RESOURCE GROUP regress_rg_x WITH (concurrency=1, cpu_max_percent=5);
+ALTER RESOURCE GROUP regress_rg_x SET cpu_max_percent 6;
+DROP RESOURCE GROUP regress_rg_x;
+RESET ROLE;
+DROP ROLE regress_rg_noadmin;
+
+-- ---------------------------------------------------------------------
+-- Cleanup.
+-- ---------------------------------------------------------------------
+DROP ROLE mdb_admin;
diff --git a/src/backend/catalog/oid_dispatch.c 
b/src/backend/catalog/oid_dispatch.c
index 6f39a07857e..af49156bdde 100644
--- a/src/backend/catalog/oid_dispatch.c
+++ b/src/backend/catalog/oid_dispatch.c
@@ -156,6 +156,18 @@ static MemoryContext oids_context = NULL;
 
 static bool preserve_oids_on_commit = false;
 
+/*
+ * OID to assign to the next auxiliary pg_authid role created through
+ * GetNewOidForAuthId(), or InvalidOid for the normal allocation path.
+ *
+ * This is the GPDB analogue of upstream PostgreSQL's
+ * binary_upgrade_next_pg_authid_oid (which is disabled here in favour of the
+ * generic OID pre-assignment machinery).  It lets gp_toolkit
+ * create roles such as mdb_admin with a fixed, well-known OID.  It is reset
+ * to InvalidOid as soon as it is consumed.
+ */
+Oid                    next_aux_pg_authid_oid = InvalidOid;
+
 /*
  * These will be used by the schema restoration process during binary upgrade,
  * so any new object must not use any Oid in this structure or else there will
@@ -423,6 +435,7 @@ GetNewOrPreassignedOid(Relation relation, Oid indexId, 
AttrNumber oidcolumn,
                                           OidAssignment *searchkey)
 {
        Oid                     oid;
+       Oid                     forcedOid = searchkey->oid;
 
        searchkey->catalog = RelationGetRelid(relation);
 
@@ -461,8 +474,18 @@ GetNewOrPreassignedOid(Relation relation, Oid indexId, 
AttrNumber oidcolumn,
        {
                MemoryContext oldcontext;
 
-               /* Assign a new oid, and memorize it in the list of OIDs to 
dispatch */
-               oid = GetNewOidWithIndex(relation, indexId, oidcolumn);
+               /*
+                * Assign a new oid, and memorize it in the list of OIDs to 
dispatch.
+                *
+                * A caller may request a fixed, well-known OID by passing it in
+                * searchkey->oid (e.g. the mdb_admin auxiliary role, see
+                * GetNewOidForAuthId()).  In that case use the requested OID 
instead
+                * of allocating a fresh one, but still record it for dispatch 
so the
+                * QEs end up with the same OID.
+                */
+               oid = OidIsValid(forcedOid)
+                       ? forcedOid
+                       : GetNewOidWithIndex(relation, indexId, oidcolumn);
 
                oldcontext = MemoryContextSwitchTo(get_oids_context());
                searchkey->oid = oid;
@@ -479,7 +502,9 @@ GetNewOrPreassignedOid(Relation relation, Oid indexId, 
AttrNumber oidcolumn,
        }
        else
        {
-               oid = GetNewOidWithIndex(relation, indexId, oidcolumn);
+               oid = OidIsValid(forcedOid)
+                       ? forcedOid
+                       : GetNewOidWithIndex(relation, indexId, oidcolumn);
        }
 
        return oid;
@@ -572,6 +597,25 @@ GetNewOidForAuthId(Relation relation, Oid indexId, 
AttrNumber oidcolumn,
        memset(&key, 0, sizeof(OidAssignment));
        key.type = T_OidAssignment;
        key.objname = rolname;
+
+       /*
+        * Allow auxiliary roles (such as mdb_admin, see gpcontrib/gp_toolkit)
+        * to be created with a fixed, well-known OID.  The OID is supplied 
through
+        * the next_aux_pg_authid_oid override, mirroring how upstream 
PostgreSQL
+        * assigns role OIDs during binary upgrade.  We only honor it for OIDs 
in
+        * the auxiliary range to avoid clashing with normal OID allocation, and
+        * reset it immediately so it affects a single role only.
+        */
+       if (OidIsValid(next_aux_pg_authid_oid))
+       {
+               if (!IsAuxOid(next_aux_pg_authid_oid))
+                       elog(ERROR, "pre-assigned auxiliary role OID %u is out 
of the auxiliary OID range",
+                                next_aux_pg_authid_oid);
+
+               key.oid = next_aux_pg_authid_oid;
+               next_aux_pg_authid_oid = InvalidOid;
+       }
+
        return GetNewOrPreassignedOid(relation, indexId, oidcolumn, &key);
 }
 
diff --git a/src/backend/commands/resgroupcmds.c 
b/src/backend/commands/resgroupcmds.c
index b62917cf3eb..77cf73d250f 100644
--- a/src/backend/commands/resgroupcmds.c
+++ b/src/backend/commands/resgroupcmds.c
@@ -34,6 +34,7 @@
 #include "commands/resgroupcmds.h"
 #include "miscadmin.h"
 #include "nodes/pg_list.h"
+#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datetime.h"
 #include "utils/fmgroids.h"
@@ -103,11 +104,11 @@ CreateResourceGroup(CreateResourceGroupStmt *stmt)
        int                     nResGroups;
        MemoryContext oldContext;
 
-       /* Permission check - only superuser can create groups. */
-       if (!superuser())
+       /* Permission check - only superuser or mdb_admin can create groups. */
+       if (!is_member_of_role(GetUserId(), MDB_ADMIN_ROLEID))
                ereport(ERROR,
                                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("must be superuser to create resource 
groups")));
+                                errmsg("must be mdb_admin to create resource 
groups")));
 
        /*
         * Check for an illegal name ('none' is used to signify no group in 
ALTER ROLE).
@@ -269,11 +270,11 @@ DropResourceGroup(DropResourceGroupStmt *stmt)
        Oid                      groupid;
        ResourceGroupCallbackContext    *callbackCtx;
 
-       /* Permission check - only superuser can drop resource groups. */
-       if (!superuser())
+       /* Permission check - only superuser or mdb_admin can drop resource 
groups. */
+       if (!is_member_of_role(GetUserId(), MDB_ADMIN_ROLEID))
                ereport(ERROR,
                                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("must be superuser to drop resource 
groups")));
+                                errmsg("must be mdb_admin to drop resource 
groups")));
 
        /*
         * Check the pg_resgroup relation to be certain the resource group 
already
@@ -302,6 +303,13 @@ DropResourceGroup(DropResourceGroupStmt *stmt)
         */
        groupid = ((Form_pg_resgroup) GETSTRUCT(tuple))->oid;
 
+       /* Permission check - only superuser can drop the admin/system resource 
groups. */
+       if (!superuser() && (groupid == ADMINRESGROUP_OID || groupid == 
SYSTEMRESGROUP_OID))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                errmsg("must be superuser to drop resource 
group \"%s\"",
+                                               stmt->name)));
+
        /* cannot DROP default resource groups  */
        if (groupid == DEFAULTRESGROUP_OID
                || groupid == ADMINRESGROUP_OID
@@ -375,11 +383,24 @@ AlterResourceGroup(AlterResourceGroupStmt *stmt)
        ResourceGroupCallbackContext    *callbackCtx;
        MemoryContext oldContext;
 
-       /* Permission check - only superuser can alter resource groups. */
-       if (!superuser())
+       /* Permission check - only mdb_admin can alter resource groups. */
+       if (!is_member_of_role(GetUserId(), MDB_ADMIN_ROLEID))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                errmsg("must be mdb_admin to alter resource 
groups")));
+
+       /*
+        * Check the pg_resgroup relation to be certain the resource group 
already
+        * exists.
+        */
+       groupid = get_resgroup_oid(stmt->name, false);
+
+       /* Permission check - only superuser can alter the admin/system 
resource groups. */
+       if (!superuser() && (groupid == ADMINRESGROUP_OID || groupid == 
SYSTEMRESGROUP_OID))
                ereport(ERROR,
                                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("must be superuser to alter resource 
groups")));
+                                errmsg("must be superuser to alter resource 
group \"%s\"",
+                                               stmt->name)));
 
        /* Currently we only support to ALTER one limit at one time */
        Assert(list_length(stmt->options) == 1);
@@ -406,12 +427,6 @@ AlterResourceGroup(AlterResourceGroupStmt *stmt)
                checkResgroupCapLimit(limitType, value);
        }
 
-       /*
-        * Check the pg_resgroup relation to be certain the resource group 
already
-        * exists.
-        */
-       groupid = get_resgroup_oid(stmt->name, false);
-
        if (limitType == RESGROUP_LIMIT_TYPE_CONCURRENCY &&
                value == 0 &&
                groupid == ADMINRESGROUP_OID)
@@ -500,7 +515,7 @@ AlterResourceGroup(AlterResourceGroupStmt *stmt)
                                                                          
RESGROUP_DEFAULT_CPU_WEIGHT, "");
 
                updateResgroupCapabilityEntry(pg_resgroupcapability_rel,
-                                                                         
groupid, RESGROUP_LIMIT_TYPE_CPUSET, 
+                                                                         
groupid, RESGROUP_LIMIT_TYPE_CPUSET,
                                                                          0, 
caps.cpuset);
        }
        else if (limitType == RESGROUP_LIMIT_TYPE_CPU)
@@ -1007,7 +1022,7 @@ parseStmtOptions(CreateResourceGroupStmt *stmt, 
ResGroupCaps *caps)
                else
                        mask |= 1 << type;
 
-               if (type == RESGROUP_LIMIT_TYPE_CPUSET) 
+               if (type == RESGROUP_LIMIT_TYPE_CPUSET)
                {
                        const char *cpuset = defGetString(defel);
                        strlcpy(caps->cpuset, cpuset, sizeof(caps->cpuset));
@@ -1611,7 +1626,7 @@ checkCpuSetByRole(const char *cpuset)
  * ex:
  * cpuset = "1;4"
  * then we should assign '1' to corrdinator and '4' to segment
- * 
+ *
  * cpuset = "1"
  * assign '1' to both coordinator and segment
  */
diff --git a/src/backend/utils/resgroup/resgroup_helper.c 
b/src/backend/utils/resgroup/resgroup_helper.c
index 00aaded168d..5acca0be5e7 100644
--- a/src/backend/utils/resgroup/resgroup_helper.c
+++ b/src/backend/utils/resgroup/resgroup_helper.c
@@ -21,6 +21,7 @@
 #include "cdb/cdbvars.h"
 #include "commands/resgroupcmds.h"
 #include "storage/procarray.h"
+#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datetime.h"
 #include "utils/resgroup.h"
@@ -464,10 +465,10 @@ pg_resgroup_move_query(PG_FUNCTION_ARGS)
                                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                                                (errmsg("resource group is not 
enabled"))));
 
-       if (!superuser())
+       if (!is_member_of_role(GetUserId(), MDB_ADMIN_ROLEID))
                ereport(ERROR,
                                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                               (errmsg("must be superuser to 
move query"))));
+                                               (errmsg("must be mdb_admin to 
move query"))));
 
        if (Gp_role == GP_ROLE_DISPATCH)
        {
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 687799bec9f..0fa8e02ece5 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -208,6 +208,16 @@ FullTransactionIdAdvance(FullTransactionId *dest)
 #define FirstBinaryUpgradeReservedObjectId 9000
 #define LastBinaryUpgradeReservedObjectId 9100
 
+/*
+ * Reserve a block of OIDs for auxiliary catalog objects (such as the
+ * mdb_admin role created by gp_toolkit).  These need fixed,
+ * well-known OIDs that are stable across clusters, so they live in their own
+ * range below FirstBinaryUpgradeReservedObjectId.
+ */
+#define FirstAuxObjectId               8000
+#define LastAuxObjectId                        9000
+#define IsAuxOid(oid)  ((oid) >= FirstAuxObjectId && (oid) < LastAuxObjectId)
+
 /*
  * VariableCache is a data structure in shared memory that is used to track
  * OID and XID assignment state.  For largely historical reasons, there is
diff --git a/src/include/catalog/oid_dispatch.h 
b/src/include/catalog/oid_dispatch.h
index fbb7a14f59e..324997a399f 100644
--- a/src/include/catalog/oid_dispatch.h
+++ b/src/include/catalog/oid_dispatch.h
@@ -16,6 +16,13 @@
 #include "utils/relcache.h"
 #include "access/htup.h"
 
+/*
+ * OID to assign to the next auxiliary pg_authid role created through
+ * GetNewOidForAuthId(), or InvalidOid for normal allocation.  Set by
+ * gp_toolkit to create roles such as mdb_admin with a fixed OID.
+ */
+extern PGDLLIMPORT Oid next_aux_pg_authid_oid;
+
 /* Functions used in master */
 extern List *GetAssignedOidsForDispatch(void);
 
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 49068f04b2f..a8e0b63f93d 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -213,6 +213,14 @@ extern bool is_member_of_role_nosuper(Oid member, Oid 
role);
 extern bool is_admin_of_role(Oid member, Oid role);
 
 // -- non-upstream patch begin
+/*
+ * Fixed, well-known OID of the mdb_admin role.  The role is created by
+ * gp_toolkit (pg_create_mdb_admin_role()) with this OID, and the
+ * resource-group permission checks identify it by OID.  It lives in the
+ * auxiliary OID range (see IsAuxOid()).
+ */
+#define MDB_ADMIN_ROLEID       8067
+
 extern bool mdb_admin_allow_bypass_owner_checks(Oid userId,  Oid ownerId);
 
 extern void check_mdb_admin_is_member_of_role(Oid member, Oid role);
diff --git a/src/test/isolation2/expected/resgroup/resgroup_mdb_admin.out 
b/src/test/isolation2/expected/resgroup/resgroup_mdb_admin.out
new file mode 100644
index 00000000000..bc9749cee0e
--- /dev/null
+++ b/src/test/isolation2/expected/resgroup/resgroup_mdb_admin.out
@@ -0,0 +1,128 @@
+-- Tests permission checks for the mdb_admin role with
+-- resource groups enabled.
+
+-- start_matchsubs
+-- m/ERROR:  cannot find process: \d+/
+-- s/\d+/XXX/g
+-- end_matchsubs
+
+DROP ROLE IF EXISTS role_rg_admin;
+DROP
+DROP ROLE IF EXISTS role_rg_noadmin;
+DROP
+DROP ROLE IF EXISTS mdb_admin;
+DROP
+-- start_ignore
+DROP RESOURCE GROUP rg_perm_admin1;
+DROP RESOURCE GROUP rg_perm_admin2;
+DROP RESOURCE GROUP rg_perm_revoke1;
+DROP RESOURCE GROUP rg_perm_revoke2;
+DROP RESOURCE GROUP rg_perm_test;
+-- end_ignore
+
+-- ---------------------------------------------------------------------
+-- Setup.  mdb_admin is identified by its fixed OID, so it must be created
+-- through gp_toolkit.pg_create_mdb_admin_role() (a plain CREATE ROLE would
+-- assign a different OID and the permission checks would not recognise its
+-- members).
+-- ---------------------------------------------------------------------
+CREATE RESOURCE GROUP rg_perm_test WITH (concurrency=2, cpu_max_percent=10);
+CREATE
+SELECT gp_toolkit.pg_create_mdb_admin_role();
+ pg_create_mdb_admin_role 
+--------------------------
+ 8067                     
+(1 row)
+CREATE ROLE role_rg_admin RESOURCE GROUP rg_perm_test;
+CREATE
+CREATE ROLE role_rg_noadmin RESOURCE GROUP rg_perm_test;
+CREATE
+GRANT mdb_admin TO role_rg_admin;
+GRANT
+
+-- ---------------------------------------------------------------------
+-- 1. Member of mdb_admin can CREATE/ALTER/DROP resource groups
+--    (statements are dispatched to segments).
+-- ---------------------------------------------------------------------
+1: SET ROLE role_rg_admin;
+SET
+1: CREATE RESOURCE GROUP rg_perm_admin1 WITH (concurrency=1, 
cpu_max_percent=5);
+CREATE
+1: ALTER RESOURCE GROUP rg_perm_admin1 SET cpu_max_percent 6;
+ALTER
+1: DROP RESOURCE GROUP rg_perm_admin1;
+DROP
+
+-- 2. Even a member cannot ALTER or DROP the system admin_group.
+1: ALTER RESOURCE GROUP admin_group SET cpu_max_percent 99;
+ERROR:  must be superuser to alter resource group "admin_group"
+1: DROP RESOURCE GROUP admin_group;
+ERROR:  must be superuser to drop resource group "admin_group"
+1q: ... <quitting>
+
+-- ---------------------------------------------------------------------
+-- 3. A non-member is rejected on every entry point.
+-- ---------------------------------------------------------------------
+2: SET ROLE role_rg_noadmin;
+SET
+2: CREATE RESOURCE GROUP rg_perm_admin2 WITH (concurrency=1, 
cpu_max_percent=5);
+ERROR:  must be mdb_admin to create resource groups
+2: ALTER RESOURCE GROUP rg_perm_test SET cpu_max_percent 7;
+ERROR:  must be mdb_admin to alter resource groups
+2: DROP RESOURCE GROUP rg_perm_test;
+ERROR:  must be mdb_admin to drop resource groups
+2q: ... <quitting>
+
+-- ---------------------------------------------------------------------
+-- 4. pg_resgroup_move_query() honours the same permission check.
+--    The first call (non-member) must fail with "must be mdb_admin".
+--    The second call (member) gets past the permission gate and
+--    fails on the pid lookup (masked by start_matchsubs above).
+-- ---------------------------------------------------------------------
+3: SET ROLE role_rg_noadmin;
+SET
+3: SELECT pg_resgroup_move_query(999999999, 'admin_group');
+ERROR:  must be mdb_admin to move query
+3: RESET ROLE;
+RESET
+3: SET ROLE role_rg_admin;
+SET
+3: SELECT pg_resgroup_move_query(999999999, 'admin_group');
+ERROR:  cannot find process: XXX
+3q: ... <quitting>
+
+-- ---------------------------------------------------------------------
+-- 5. Cross-session REVOKE takes effect on the granted session's
+--    next statement (the privilege is re-checked per command, not
+--    cached at SET ROLE time).
+-- ---------------------------------------------------------------------
+4: SET ROLE role_rg_admin;
+SET
+4: CREATE RESOURCE GROUP rg_perm_revoke1 WITH (concurrency=1, 
cpu_max_percent=5);
+CREATE
+5: REVOKE mdb_admin FROM role_rg_admin;
+REVOKE
+4: CREATE RESOURCE GROUP rg_perm_revoke2 WITH (concurrency=1, 
cpu_max_percent=5);
+ERROR:  must be mdb_admin to create resource groups
+4: DROP RESOURCE GROUP rg_perm_revoke1;
+ERROR:  must be mdb_admin to drop resource groups
+4q: ... <quitting>
+5q: ... <quitting>
+
+-- ---------------------------------------------------------------------
+-- Cleanup. Roles must be dropped before the resource group they
+-- reference, otherwise DROP RESOURCE GROUP fails with
+-- "resource group is used by at least one role".
+-- ---------------------------------------------------------------------
+RESET ROLE;
+RESET
+DROP ROLE role_rg_admin;
+DROP
+DROP ROLE role_rg_noadmin;
+DROP
+DROP ROLE mdb_admin;
+DROP
+DROP RESOURCE GROUP rg_perm_revoke1;
+DROP
+DROP RESOURCE GROUP rg_perm_test;
+DROP
diff --git a/src/test/isolation2/isolation2_resgroup_v1_schedule 
b/src/test/isolation2/isolation2_resgroup_v1_schedule
index f5dea2d4012..792ab2642bc 100644
--- a/src/test/isolation2/isolation2_resgroup_v1_schedule
+++ b/src/test/isolation2/isolation2_resgroup_v1_schedule
@@ -35,6 +35,9 @@ test: resgroup/resgroup_move_query
 test: resgroup/resgroup_recreate
 test: resgroup/resgroup_functions
 
+# mdb_admin permission checks
+test: resgroup/resgroup_mdb_admin
+
 # dump info
 test: resgroup/resgroup_dumpinfo
 
diff --git a/src/test/isolation2/isolation2_resgroup_v2_schedule 
b/src/test/isolation2/isolation2_resgroup_v2_schedule
index 0190efcd3d8..a422e0115f3 100644
--- a/src/test/isolation2/isolation2_resgroup_v2_schedule
+++ b/src/test/isolation2/isolation2_resgroup_v2_schedule
@@ -40,6 +40,9 @@ test: resgroup/resgroup_functions
 #test: resgroup/restore_default_resgroup
 #test: resgroup/resgroup_parallel_queries
 
+# mdb_admin permission checks
+test: resgroup/resgroup_mdb_admin
+
 # dump info
 test: resgroup/resgroup_dumpinfo
 
diff --git a/src/test/isolation2/sql/resgroup/resgroup_mdb_admin.sql 
b/src/test/isolation2/sql/resgroup/resgroup_mdb_admin.sql
new file mode 100644
index 00000000000..9db82b44906
--- /dev/null
+++ b/src/test/isolation2/sql/resgroup/resgroup_mdb_admin.sql
@@ -0,0 +1,91 @@
+-- Tests permission checks for the mdb_admin role with
+-- resource groups enabled.
+
+-- start_matchsubs
+-- m/ERROR:  cannot find process: \d+/
+-- s/\d+/XXX/g
+-- end_matchsubs
+
+DROP ROLE IF EXISTS role_rg_admin;
+DROP ROLE IF EXISTS role_rg_noadmin;
+DROP ROLE IF EXISTS mdb_admin;
+-- start_ignore
+DROP RESOURCE GROUP rg_perm_admin1;
+DROP RESOURCE GROUP rg_perm_admin2;
+DROP RESOURCE GROUP rg_perm_revoke1;
+DROP RESOURCE GROUP rg_perm_revoke2;
+DROP RESOURCE GROUP rg_perm_test;
+-- end_ignore
+
+-- ---------------------------------------------------------------------
+-- Setup.  mdb_admin is identified by its fixed OID, so it must be created
+-- through gp_toolkit.pg_create_mdb_admin_role() (a plain CREATE ROLE would
+-- assign a different OID and the permission checks would not recognise its
+-- members).
+-- ---------------------------------------------------------------------
+CREATE RESOURCE GROUP rg_perm_test WITH (concurrency=2, cpu_max_percent=10);
+SELECT gp_toolkit.pg_create_mdb_admin_role();
+CREATE ROLE role_rg_admin RESOURCE GROUP rg_perm_test;
+CREATE ROLE role_rg_noadmin RESOURCE GROUP rg_perm_test;
+GRANT mdb_admin TO role_rg_admin;
+
+-- ---------------------------------------------------------------------
+-- 1. Member of mdb_admin can CREATE/ALTER/DROP resource groups
+--    (statements are dispatched to segments).
+-- ---------------------------------------------------------------------
+1: SET ROLE role_rg_admin;
+1: CREATE RESOURCE GROUP rg_perm_admin1 WITH (concurrency=1, 
cpu_max_percent=5);
+1: ALTER RESOURCE GROUP rg_perm_admin1 SET cpu_max_percent 6;
+1: DROP RESOURCE GROUP rg_perm_admin1;
+
+-- 2. Even a member cannot ALTER or DROP the system admin_group.
+1: ALTER RESOURCE GROUP admin_group SET cpu_max_percent 99;
+1: DROP RESOURCE GROUP admin_group;
+1q:
+
+-- ---------------------------------------------------------------------
+-- 3. A non-member is rejected on every entry point.
+-- ---------------------------------------------------------------------
+2: SET ROLE role_rg_noadmin;
+2: CREATE RESOURCE GROUP rg_perm_admin2 WITH (concurrency=1, 
cpu_max_percent=5);
+2: ALTER RESOURCE GROUP rg_perm_test SET cpu_max_percent 7;
+2: DROP RESOURCE GROUP rg_perm_test;
+2q:
+
+-- ---------------------------------------------------------------------
+-- 4. pg_resgroup_move_query() honours the same permission check.
+--    The first call (non-member) must fail with "must be mdb_admin".
+--    The second call (member) gets past the permission gate and
+--    fails on the pid lookup (masked by start_matchsubs above).
+-- ---------------------------------------------------------------------
+3: SET ROLE role_rg_noadmin;
+3: SELECT pg_resgroup_move_query(999999999, 'admin_group');
+3: RESET ROLE;
+3: SET ROLE role_rg_admin;
+3: SELECT pg_resgroup_move_query(999999999, 'admin_group');
+3q:
+
+-- ---------------------------------------------------------------------
+-- 5. Cross-session REVOKE takes effect on the granted session's
+--    next statement (the privilege is re-checked per command, not
+--    cached at SET ROLE time).
+-- ---------------------------------------------------------------------
+4: SET ROLE role_rg_admin;
+4: CREATE RESOURCE GROUP rg_perm_revoke1 WITH (concurrency=1, 
cpu_max_percent=5);
+5: REVOKE mdb_admin FROM role_rg_admin;
+4: CREATE RESOURCE GROUP rg_perm_revoke2 WITH (concurrency=1, 
cpu_max_percent=5);
+4: DROP RESOURCE GROUP rg_perm_revoke1;
+4q:
+5q:
+
+-- ---------------------------------------------------------------------
+-- Cleanup. Roles must be dropped before the resource group they
+-- reference, otherwise DROP RESOURCE GROUP fails with
+-- "resource group is used by at least one role".
+-- ---------------------------------------------------------------------
+RESET ROLE;
+DROP ROLE role_rg_admin;
+DROP ROLE role_rg_noadmin;
+DROP ROLE mdb_admin;
+DROP RESOURCE GROUP rg_perm_revoke1;
+DROP RESOURCE GROUP rg_perm_test;


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

Reply via email to