diff --git a/ddl/BUILD.bazel b/ddl/BUILD.bazel index 747a7c25c0f12..32387949c14c3 100644 --- a/ddl/BUILD.bazel +++ b/ddl/BUILD.bazel @@ -80,6 +80,7 @@ go_library( "//parser/opcode", "//parser/terror", "//parser/types", + "//privilege", "//sessionctx", "//sessionctx/binloginfo", "//sessionctx/stmtctx", diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index c2a319f30b059..3e256e5306fc8 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -47,6 +47,7 @@ import ( "github.com/pingcap/tidb/parser/mysql" "github.com/pingcap/tidb/parser/terror" field_types "github.com/pingcap/tidb/parser/types" + "github.com/pingcap/tidb/privilege" "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/sessiontxn" @@ -7668,6 +7669,17 @@ func (d *ddl) DropResourceGroup(ctx sessionctx.Context, stmt *ast.DropResourceGr return err } + // check to see if some user has dependency on the group + checker := privilege.GetPrivilegeManager(ctx) + if checker == nil { + return errors.New("miss privilege checker") + } + user, matched := checker.MatchUserResourceGroupName(groupName.L) + if matched { + err = errors.Errorf("user [%s] depends on the resource group to drop", user) + return err + } + job := &model.Job{ SchemaID: group.ID, SchemaName: group.Name.L, diff --git a/ddl/resource_group_test.go b/ddl/resource_group_test.go index 0693247b25ce1..789e81f99f0fb 100644 --- a/ddl/resource_group_test.go +++ b/ddl/resource_group_test.go @@ -160,6 +160,10 @@ func TestResourceGroupBasic(t *testing.T) { tk.MustGetErrCode("create user usr_fail resource group nil_group", mysql.ErrResourceGroupNotExists) tk.MustExec("create user user2") tk.MustGetErrCode("alter user user2 resource group nil_group", mysql.ErrResourceGroupNotExists) + + tk.MustExec("create resource group do_not_delete_rg rru_per_sec=100 wru_per_sec=200") + tk.MustExec("create user usr3 resource group do_not_delete_rg") + tk.MustContainErrMsg("drop resource group do_not_delete_rg", "user [usr3] depends on the resource group to drop") } func testResourceGroupNameFromIS(t *testing.T, ctx sessionctx.Context, name string) *model.ResourceGroupInfo { diff --git a/errno/errname.go b/errno/errname.go index a75ac71a6b212..39deb486ff2fb 100644 --- a/errno/errname.go +++ b/errno/errname.go @@ -1103,7 +1103,7 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{ ErrResourceGroupNotExists: mysql.Message("Unknown resource group '%-.192s'", nil), ErrColumnInChange: mysql.Message("column %s id %d does not exist, this column may have been updated by other DDL ran in parallel", nil), - ErrResourceGroupSupportDisabled: mysql.Message("Resource group feature is disabled", nil), + ErrResourceGroupSupportDisabled: mysql.Message("Resource control feature is disabled. Run `SET GLOBAL tidb_enable_resource_control='on'` to enable the feature", nil), // TiKV/PD errors. ErrPDServerTimeout: mysql.Message("PD server timeout: %s", nil), diff --git a/errors.toml b/errors.toml index f6f484984f8b5..d7808c34337a1 100644 --- a/errors.toml +++ b/errors.toml @@ -2538,7 +2538,7 @@ Unknown resource group '%-.192s' ["schema:8250"] error = ''' -Resource group feature is disabled +Resource control feature is disabled. Run `SET GLOBAL tidb_enable_resource_control='on'` to enable the feature ''' ["session:8002"] diff --git a/privilege/privilege.go b/privilege/privilege.go index 47a6303ad2212..3229c1e3bb26f 100644 --- a/privilege/privilege.go +++ b/privilege/privilege.go @@ -87,6 +87,9 @@ type Manager interface { // MatchIdentity matches an identity MatchIdentity(user, host string, skipNameResolve bool) (string, string, bool) + // MatchUserResourceGroupName matches a user with specified resource group name + MatchUserResourceGroupName(resourceGroupName string) (string, bool) + // DBIsVisible returns true is the database is visible to current user. DBIsVisible(activeRole []*auth.RoleIdentity, db string) bool diff --git a/privilege/privileges/cache.go b/privilege/privileges/cache.go index caadb6e4f7397..9159777340b67 100644 --- a/privilege/privileges/cache.go +++ b/privilege/privileges/cache.go @@ -1014,6 +1014,17 @@ func (p *MySQLPrivilege) matchIdentity(user, host string, skipNameResolve bool) return nil } +// matchResoureGroup finds an identity to match resource group. +func (p *MySQLPrivilege) matchResoureGroup(resourceGroupName string) *UserRecord { + for i := 0; i < len(p.User); i++ { + record := &p.User[i] + if record.ResourceGroup == resourceGroupName { + return record + } + } + return nil +} + // connectionVerification verifies the username + hostname according to exact // match from the mysql.user privilege table. call matchIdentity() first if you // do not have an exact match yet. diff --git a/privilege/privileges/privileges.go b/privilege/privileges/privileges.go index 288415c7bcdb4..af038f984f413 100644 --- a/privilege/privileges/privileges.go +++ b/privilege/privileges/privileges.go @@ -310,6 +310,16 @@ func (p *UserPrivileges) MatchIdentity(user, host string, skipNameResolve bool) return "", "", false } +// MatchUserResourceGroupName implements the Manager interface. +func (p *UserPrivileges) MatchUserResourceGroupName(resourceGroupName string) (u string, success bool) { + mysqlPriv := p.Handle.Get() + record := mysqlPriv.matchResoureGroup(resourceGroupName) + if record != nil { + return record.User, true + } + return "", false +} + // GetAuthWithoutVerification implements the Manager interface. func (p *UserPrivileges) GetAuthWithoutVerification(user, host string) (success bool) { if SkipWithGrant {