diff --git a/internal/metastore/catalog.go b/internal/metastore/catalog.go index 6ad376fc6d..93248f6e83 100644 --- a/internal/metastore/catalog.go +++ b/internal/metastore/catalog.go @@ -40,8 +40,6 @@ type RootCoordCatalog interface { // GetCredential gets the credential info for the username, returns error if no credential exists for this username. GetCredential(ctx context.Context, username string) (*model.Credential, error) - // CreateCredential creates credential by Username and EncryptedPassword in crediential. Please make sure credential.Username isn't empty before calling this API. Credentials already exists will be altered. - CreateCredential(ctx context.Context, credential *model.Credential) error // AlterCredential does exactly the same as CreateCredential AlterCredential(ctx context.Context, credential *model.Credential) error // DropCredential removes the credential of this username diff --git a/internal/metastore/kv/rootcoord/kv_catalog.go b/internal/metastore/kv/rootcoord/kv_catalog.go index 4df8ee94f4..34cd39f680 100644 --- a/internal/metastore/kv/rootcoord/kv_catalog.go +++ b/internal/metastore/kv/rootcoord/kv_catalog.go @@ -346,9 +346,11 @@ func (kc *Catalog) CreateAlias(ctx context.Context, alias *model.Alias, ts typeu return kc.Snapshot.MultiSaveAndRemove(ctx, kvs, []string{oldKBefore210, oldKeyWithoutDb}, ts) } -func (kc *Catalog) CreateCredential(ctx context.Context, credential *model.Credential) error { +func (kc *Catalog) AlterCredential(ctx context.Context, credential *model.Credential) error { k := fmt.Sprintf("%s/%s", CredentialPrefix, credential.Username) - v, err := json.Marshal(&internalpb.CredentialInfo{EncryptedPassword: credential.EncryptedPassword}) + credentialInfo := model.MarshalCredentialModel(credential) + credentialInfo.Username = "" // Username is already save in the key, remove it from the value. + v, err := json.Marshal(credentialInfo) if err != nil { log.Ctx(ctx).Error("create credential marshal fail", zap.String("key", k), zap.Error(err)) return err @@ -359,14 +361,9 @@ func (kc *Catalog) CreateCredential(ctx context.Context, credential *model.Crede log.Ctx(ctx).Error("create credential persist meta fail", zap.String("key", k), zap.Error(err)) return err } - return nil } -func (kc *Catalog) AlterCredential(ctx context.Context, credential *model.Credential) error { - return kc.CreateCredential(ctx, credential) -} - func (kc *Catalog) listPartitionsAfter210(ctx context.Context, collectionID typeutil.UniqueID, ts typeutil.Timestamp) ([]*model.Partition, error) { prefix := BuildPartitionPrefix(collectionID) _, values, err := kc.Snapshot.LoadWithPrefix(ctx, prefix, ts) @@ -625,8 +622,9 @@ func (kc *Catalog) GetCredential(ctx context.Context, username string) (*model.C if err != nil { return nil, fmt.Errorf("unmarshal credential info err:%w", err) } - - return &model.Credential{Username: username, EncryptedPassword: credentialInfo.EncryptedPassword}, nil + // we don't save the username in the credential info, so we need to set it manually from path. + credentialInfo.Username = username + return model.UnmarshalCredentialModel(&credentialInfo), nil } func (kc *Catalog) AlterAlias(ctx context.Context, alias *model.Alias, ts typeutil.Timestamp) error { @@ -1050,18 +1048,6 @@ func (kc *Catalog) ListCredentialsWithPasswd(ctx context.Context) (map[string]st return users, nil } -func (kc *Catalog) save(ctx context.Context, k string) error { - var err error - if _, err = kc.Txn.Load(ctx, k); err != nil && !errors.Is(err, merr.ErrIoKeyNotFound) { - return err - } - if err == nil { - log.Ctx(ctx).Debug("the key has existed", zap.String("key", k)) - return common.NewIgnorableError(fmt.Errorf("the key[%s] has existed", k)) - } - return kc.Txn.Save(ctx, k, "") -} - func (kc *Catalog) remove(ctx context.Context, k string) error { var err error if _, err = kc.Txn.Load(ctx, k); err != nil && !errors.Is(err, merr.ErrIoKeyNotFound) { @@ -1076,11 +1062,7 @@ func (kc *Catalog) remove(ctx context.Context, k string) error { func (kc *Catalog) CreateRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error { k := funcutil.HandleTenantForEtcdKey(RolePrefix, tenant, entity.Name) - err := kc.save(ctx, k) - if err != nil && !common.IsIgnorableError(err) { - log.Ctx(ctx).Warn("fail to save the role", zap.String("key", k), zap.Error(err)) - } - return err + return kc.Txn.Save(ctx, k, "") } func (kc *Catalog) DropRole(ctx context.Context, tenant string, roleName string) error { @@ -1112,21 +1094,13 @@ func (kc *Catalog) DropRole(ctx context.Context, tenant string, roleName string) func (kc *Catalog) AlterUserRole(ctx context.Context, tenant string, userEntity *milvuspb.UserEntity, roleEntity *milvuspb.RoleEntity, operateType milvuspb.OperateUserRoleType) error { k := funcutil.HandleTenantForEtcdKey(RoleMappingPrefix, tenant, fmt.Sprintf("%s/%s", userEntity.Name, roleEntity.Name)) - var err error - if operateType == milvuspb.OperateUserRoleType_AddUserToRole { - err = kc.save(ctx, k) - if err != nil { - log.Ctx(ctx).Error("fail to save the user-role", zap.String("key", k), zap.Error(err)) - } - } else if operateType == milvuspb.OperateUserRoleType_RemoveUserFromRole { - err = kc.remove(ctx, k) - if err != nil { - log.Ctx(ctx).Error("fail to remove the user-role", zap.String("key", k), zap.Error(err)) - } - } else { - err = fmt.Errorf("invalid operate user role type, operate type: %d", operateType) + switch operateType { + case milvuspb.OperateUserRoleType_AddUserToRole: + return kc.Txn.Save(ctx, k, "") + case milvuspb.OperateUserRoleType_RemoveUserFromRole: + return kc.Txn.Remove(ctx, k) } - return err + return fmt.Errorf("invalid operate user role type, operate type: %d", operateType) } func (kc *Catalog) ListRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) { @@ -1593,145 +1567,49 @@ func (kc *Catalog) BackupRBAC(ctx context.Context, tenant string) (*milvuspb.RBA } func (kc *Catalog) RestoreRBAC(ctx context.Context, tenant string, meta *milvuspb.RBACMeta) error { - var err error - needRollbackUser := make([]*milvuspb.UserInfo, 0) - needRollbackRole := make([]*milvuspb.RoleEntity, 0) - needRollbackGrants := make([]*milvuspb.GrantEntity, 0) - needRollbackPrivilegeGroups := make([]*milvuspb.PrivilegeGroupInfo, 0) - defer func() { - if err != nil { - log.Ctx(ctx).Warn("failed to restore rbac, try to rollback", zap.Error(err)) - // roll back role - for _, role := range needRollbackRole { - err = kc.DropRole(ctx, tenant, role.GetName()) - if err != nil { - log.Ctx(ctx).Warn("failed to rollback roles after restore failed", zap.Error(err)) - } - } - - // roll back grant - for _, grant := range needRollbackGrants { - err = kc.AlterGrant(ctx, tenant, grant, milvuspb.OperatePrivilegeType_Revoke) - if err != nil { - log.Ctx(ctx).Warn("failed to rollback grants after restore failed", zap.Error(err)) - } - } - - for _, user := range needRollbackUser { - // roll back user - err = kc.DropCredential(ctx, user.GetUser()) - if err != nil { - log.Ctx(ctx).Warn("failed to rollback users after restore failed", zap.Error(err)) - } - } - - // roll back privilege group - for _, group := range needRollbackPrivilegeGroups { - err = kc.DropPrivilegeGroup(ctx, group.GetGroupName()) - if err != nil { - log.Ctx(ctx).Warn("failed to rollback privilege groups after restore failed", zap.Error(err)) - } - } - } - }() - - // restore role - existRoles, err := kc.ListRole(ctx, tenant, nil, false) - if err != nil { - return err - } - existRoleMap := lo.SliceToMap(existRoles, func(entity *milvuspb.RoleResult) (string, struct{}) { return entity.GetRole().GetName(), struct{}{} }) for _, role := range meta.GetRoles() { - if _, ok := existRoleMap[role.GetName()]; ok { - log.Ctx(ctx).Warn("failed to restore, role already exists", zap.String("role", role.GetName())) - err = errors.Newf("role [%s] already exists", role.GetName()) - return err + if err := kc.CreateRole(ctx, tenant, role); err != nil { + return errors.Wrap(err, "failed to create role") } - err = kc.CreateRole(ctx, tenant, role) - if err != nil { - return err - } - needRollbackRole = append(needRollbackRole, role) } - // restore privilege group - existPrivGroups, err := kc.ListPrivilegeGroups(ctx) - if err != nil { - return err - } - existPrivGroupMap := lo.SliceToMap(existPrivGroups, func(entity *milvuspb.PrivilegeGroupInfo) (string, struct{}) { return entity.GetGroupName(), struct{}{} }) for _, group := range meta.GetPrivilegeGroups() { - if _, ok := existPrivGroupMap[group.GetGroupName()]; ok { - log.Ctx(ctx).Warn("failed to restore, privilege group already exists", zap.String("group", group.GetGroupName())) - err = errors.Newf("privilege group [%s] already exists", group.GetGroupName()) - return err + if err := kc.SavePrivilegeGroup(ctx, group); err != nil { + return errors.Wrap(err, "failed to save privilege group") } - err = kc.SavePrivilegeGroup(ctx, group) - if err != nil { - return err - } - needRollbackPrivilegeGroups = append(needRollbackPrivilegeGroups, group) } - // restore grant, list latest privilege group first - existPrivGroups, err = kc.ListPrivilegeGroups(ctx) - if err != nil { - return err - } - existPrivGroupMap = lo.SliceToMap(existPrivGroups, func(entity *milvuspb.PrivilegeGroupInfo) (string, struct{}) { return entity.GetGroupName(), struct{}{} }) for _, grant := range meta.GetGrants() { privName := grant.GetGrantor().GetPrivilege().GetName() if util.IsPrivilegeNameDefined(privName) { grant.Grantor.Privilege.Name = util.PrivilegeNameForMetastore(privName) - } else if _, ok := existPrivGroupMap[privName]; ok { - grant.Grantor.Privilege.Name = util.PrivilegeGroupNameForMetastore(privName) } else { - log.Ctx(ctx).Warn("failed to restore, privilege group does not exist", zap.String("group", privName)) - err = errors.Newf("privilege group [%s] does not exist", privName) - return err + grant.Grantor.Privilege.Name = util.PrivilegeGroupNameForMetastore(privName) } - err = kc.AlterGrant(ctx, tenant, grant, milvuspb.OperatePrivilegeType_Grant) - if err != nil { - return err + if err := kc.AlterGrant(ctx, tenant, grant, milvuspb.OperatePrivilegeType_Grant); err != nil { + return errors.Wrap(err, "failed to alter grant") } - needRollbackGrants = append(needRollbackGrants, grant) } - // need rollback user - existUser, err := kc.ListUser(ctx, tenant, nil, false) - if err != nil { - return err - } - existUserMap := lo.SliceToMap(existUser, func(entity *milvuspb.UserResult) (string, struct{}) { return entity.GetUser().GetName(), struct{}{} }) for _, user := range meta.GetUsers() { - if _, ok := existUserMap[user.GetUser()]; ok { - log.Ctx(ctx).Info("failed to restore, user already exists", zap.String("user", user.GetUser())) - err = errors.Newf("user [%s] already exists", user.GetUser()) - return err - } - // restore user - err = kc.CreateCredential(ctx, &model.Credential{ + if err := kc.AlterCredential(ctx, &model.Credential{ Username: user.GetUser(), EncryptedPassword: user.GetPassword(), - }) - if err != nil { - return err + }); err != nil { + return errors.Wrap(err, "failed to alter credential") } - needRollbackUser = append(needRollbackUser, user) // restore user role mapping entity := &milvuspb.UserEntity{ Name: user.GetUser(), } for _, role := range user.GetRoles() { - err = kc.AlterUserRole(ctx, tenant, entity, role, milvuspb.OperateUserRoleType_AddUserToRole) - if err != nil { - return err + if err := kc.AlterUserRole(ctx, tenant, entity, role, milvuspb.OperateUserRoleType_AddUserToRole); err != nil { + return errors.Wrap(err, "failed to alter user role") } } } - - return err + return nil } func (kc *Catalog) GetPrivilegeGroup(ctx context.Context, groupName string) (*milvuspb.PrivilegeGroupInfo, error) { diff --git a/internal/metastore/kv/rootcoord/kv_catalog_test.go b/internal/metastore/kv/rootcoord/kv_catalog_test.go index 412f671a0e..097bcf782d 100644 --- a/internal/metastore/kv/rootcoord/kv_catalog_test.go +++ b/internal/metastore/kv/rootcoord/kv_catalog_test.go @@ -1564,7 +1564,7 @@ func TestRBAC_Credential(t *testing.T) { for _, test := range tests { t.Run(test.description, func(t *testing.T) { - err := c.CreateCredential(ctx, &model.Credential{ + err := c.AlterCredential(ctx, &model.Credential{ Username: test.user, EncryptedPassword: test.password, }) @@ -1772,94 +1772,17 @@ func TestRBAC_Role(t *testing.T) { }) } }) - - t.Run("test save", func(t *testing.T) { - var ( - kvmock = mocks.NewTxnKV(t) - c = NewCatalog(kvmock, nil).(*Catalog) - - notExistKey = "not-exist" - errorKey = "error" - otherError = errors.New("mock load error") - ) - - kvmock.EXPECT().Load(mock.Anything, notExistKey).Return("", merr.WrapErrIoKeyNotFound(notExistKey)).Once() - kvmock.EXPECT().Load(mock.Anything, errorKey).Return("", otherError).Once() - kvmock.EXPECT().Load(mock.Anything, mock.Anything).Return("", nil).Once() - kvmock.EXPECT().Save(mock.Anything, mock.Anything, mock.Anything).Call.Return(nil).Once() - - tests := []struct { - description string - - isValid bool - key string - - expectedError error - ignorable bool - }{ - {"key not exists", true, notExistKey, nil, false}, - {"other error", false, errorKey, otherError, false}, - {"ignorable error", false, "key1", &common.IgnorableError{}, true}, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - err := c.save(ctx, test.key) - if test.isValid { - assert.NoError(t, err) - } else { - assert.Error(t, err) - } - - if test.ignorable { - _, ok := err.(*common.IgnorableError) - assert.True(t, ok) - } - }) - } - }) t.Run("test CreateRole", func(t *testing.T) { var ( kvmock = mocks.NewTxnKV(t) c = NewCatalog(kvmock, nil) - - notExistName = "not-exist" - notExistPath = funcutil.HandleTenantForEtcdKey(RolePrefix, tenant, notExistName) - errorName = "error" - errorPath = funcutil.HandleTenantForEtcdKey(RolePrefix, tenant, errorName) - otherError = errors.New("mock load error") ) - kvmock.EXPECT().Load(mock.Anything, notExistPath).Return("", merr.WrapErrIoKeyNotFound(notExistName)).Once() - kvmock.EXPECT().Load(mock.Anything, errorPath).Return("", otherError).Once() - kvmock.EXPECT().Load(mock.Anything, mock.Anything).Return("", nil).Once() kvmock.EXPECT().Save(mock.Anything, mock.Anything, mock.Anything).Call.Return(nil).Once() - - tests := []struct { - description string - - isValid bool - name string - - expectedError error - }{ - {"key not exists", true, notExistName, nil}, - {"other error", false, errorName, otherError}, - {"ignorable error", false, "key1", &common.IgnorableError{}}, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - err := c.CreateRole(ctx, tenant, &milvuspb.RoleEntity{ - Name: test.name, - }) - if test.isValid { - assert.NoError(t, err) - } else { - assert.Error(t, err) - } - }) - } + err := c.CreateRole(ctx, tenant, &milvuspb.RoleEntity{ + Name: "role1", + }) + assert.NoError(t, err) }) t.Run("test DropRole", func(t *testing.T) { var ( @@ -1920,58 +1843,17 @@ func TestRBAC_Role(t *testing.T) { c = NewCatalog(kvmock, nil) user = "default-user" - - noErrorRoleSave = "no-error-role-save" - noErrorRoleSavepath = funcutil.HandleTenantForEtcdKey(RoleMappingPrefix, tenant, fmt.Sprintf("%s/%s", user, noErrorRoleSave)) - - errorRoleSave = "error-role-save" - errorRoleSavepath = funcutil.HandleTenantForEtcdKey(RoleMappingPrefix, tenant, fmt.Sprintf("%s/%s", user, errorRoleSave)) - - errorRoleRemove = "error-role-remove" - errorRoleRemovepath = funcutil.HandleTenantForEtcdKey(RoleMappingPrefix, tenant, fmt.Sprintf("%s/%s", user, errorRoleRemove)) ) kvmock.EXPECT().Save(mock.Anything, mock.Anything, mock.Anything).Return(nil) kvmock.EXPECT().Remove(mock.Anything, mock.Anything).Return(nil) - - // Catalog.save() returns error - kvmock.EXPECT().Load(mock.Anything, errorRoleSavepath).Return("", nil) - - // Catalog.save() returns nil - kvmock.EXPECT().Load(mock.Anything, noErrorRoleSavepath).Return("", merr.WrapErrIoKeyNotFound(noErrorRoleSavepath)) - - // Catalog.remove() returns error - kvmock.EXPECT().Load(mock.Anything, errorRoleRemovepath).Return("", errors.New("not exists")) - - // Catalog.remove() returns nil - kvmock.EXPECT().Load(mock.Anything, mock.Anything).Return("", nil) - - tests := []struct { - description string - isValid bool - - role string - oType milvuspb.OperateUserRoleType - }{ - {"valid role role1, AddUserToRole", true, noErrorRoleSave, milvuspb.OperateUserRoleType_AddUserToRole}, - {"invalid role error-role, AddUserToRole", false, errorRoleSave, milvuspb.OperateUserRoleType_AddUserToRole}, - {"valid role role1, RemoveUserFromRole", true, "role", milvuspb.OperateUserRoleType_RemoveUserFromRole}, - {"invalid role error-role, RemoveUserFromRole", false, errorRoleRemove, milvuspb.OperateUserRoleType_RemoveUserFromRole}, - {"invalid operate type 100", false, "role1", milvuspb.OperateUserRoleType(100)}, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - err := c.AlterUserRole(ctx, tenant, &milvuspb.UserEntity{Name: user}, &milvuspb.RoleEntity{ - Name: test.role, - }, test.oType) - - if test.isValid { - assert.NoError(t, err) - } else { - assert.Error(t, err) - } - }) - } + err := c.AlterUserRole(ctx, tenant, &milvuspb.UserEntity{Name: user}, &milvuspb.RoleEntity{ + Name: "role1", + }, milvuspb.OperateUserRoleType_RemoveUserFromRole) + require.NoError(t, err) + err = c.AlterUserRole(ctx, tenant, &milvuspb.UserEntity{Name: user}, &milvuspb.RoleEntity{ + Name: "role1", + }, milvuspb.OperateUserRoleType_AddUserToRole) + require.NoError(t, err) }) t.Run("test ListRole", func(t *testing.T) { @@ -2816,7 +2698,7 @@ func TestRBAC_Backup(t *testing.T) { Privilege: &milvuspb.PrivilegeEntity{Name: "PrivilegeLoad"}, }, }, milvuspb.OperatePrivilegeType_Grant) - c.CreateCredential(ctx, &model.Credential{ + c.AlterCredential(ctx, &model.Credential{ Username: "user1", EncryptedPassword: "passwd", }) @@ -2940,15 +2822,6 @@ func TestRBAC_Restore(t *testing.T) { }, }, }, - { - User: "user1", - Password: "passwd", - Roles: []*milvuspb.RoleEntity{ - { - Name: "role2", - }, - }, - }, }, Roles: []*milvuspb.RoleEntity{ { @@ -2979,30 +2852,31 @@ func TestRBAC_Restore(t *testing.T) { // test restore failed and roll back err = c.RestoreRBAC(ctx, util.DefaultTenant, rbacMeta2) - assert.Error(t, err) + assert.NoError(t, err) // check user users, err = c.ListCredentialsWithPasswd(ctx) assert.NoError(t, err) - assert.Len(t, users, 1) + assert.Len(t, users, 2) // check role roles, err = c.ListRole(ctx, util.DefaultTenant, nil, false) assert.NoError(t, err) - assert.Len(t, roles, 1) + assert.Len(t, roles, 2) // check grant grants, err = c.ListGrant(ctx, util.DefaultTenant, &milvuspb.GrantEntity{ - Role: roles[0].Role, + Role: &milvuspb.RoleEntity{Name: "role2"}, DbName: util.AnyWord, }) assert.NoError(t, err) assert.Len(t, grants, 1) - assert.Equal(t, grants[0].Grantor.Privilege.Name, "Load") + assert.Equal(t, "obj_name2", grants[0].ObjectName) + assert.Equal(t, "role2", grants[0].Role.Name) + assert.Equal(t, "user2", grants[0].Grantor.User.Name) + assert.Equal(t, "Load", grants[0].Grantor.Privilege.Name) // check privilege group privGroups, err = c.ListPrivilegeGroups(ctx) assert.NoError(t, err) - assert.Len(t, privGroups, 1) - assert.Equal(t, "custom_group", privGroups[0].GroupName) - assert.Equal(t, "CreateCollection", privGroups[0].Privileges[0].Name) + assert.Len(t, privGroups, 2) } func TestRBAC_PrivilegeGroup(t *testing.T) { diff --git a/internal/metastore/model/credential.go b/internal/metastore/model/credential.go index 8fda165b3f..b952361e71 100644 --- a/internal/metastore/model/credential.go +++ b/internal/metastore/model/credential.go @@ -8,6 +8,7 @@ type Credential struct { Tenant string IsSuper bool Sha256Password string + TimeTick uint64 // the timetick in wal which the credential updates } func MarshalCredentialModel(cred *Credential) *internalpb.CredentialInfo { @@ -20,5 +21,20 @@ func MarshalCredentialModel(cred *Credential) *internalpb.CredentialInfo { EncryptedPassword: cred.EncryptedPassword, IsSuper: cred.IsSuper, Sha256Password: cred.Sha256Password, + TimeTick: cred.TimeTick, + } +} + +func UnmarshalCredentialModel(cred *internalpb.CredentialInfo) *Credential { + if cred == nil { + return nil + } + return &Credential{ + Username: cred.Username, + EncryptedPassword: cred.EncryptedPassword, + Tenant: cred.Tenant, + IsSuper: cred.IsSuper, + Sha256Password: cred.Sha256Password, + TimeTick: cred.TimeTick, } } diff --git a/internal/rootcoord/ddl_callbacks.go b/internal/rootcoord/ddl_callbacks.go new file mode 100644 index 0000000000..b5d23dd803 --- /dev/null +++ b/internal/rootcoord/ddl_callbacks.go @@ -0,0 +1,104 @@ +// Licensed to the LF AI & Data foundation 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 rootcoord + +import ( + "context" + "fmt" + + "github.com/cockroachdb/errors" + + "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster" + "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/broadcast" + "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/registry" + "github.com/milvus-io/milvus/internal/util/proxyutil" + "github.com/milvus-io/milvus/pkg/v2/proto/messagespb" + "github.com/milvus-io/milvus/pkg/v2/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v2/streaming/util/message/ce" +) + +// RegisterDDLCallbacks registers the ddl callbacks. +func RegisterDDLCallbacks(core *Core) { + ddlCallback := &DDLCallback{ + Core: core, + } + // RBAC + ddlCallback.registerRBACCallbacks() +} + +// registerRBACCallbacks registers the rbac callbacks. +func (c *DDLCallback) registerRBACCallbacks() { + registry.RegisterAlterUserV2AckCallback(c.alterUserV2AckCallback) + registry.RegisterDropUserV2AckCallback(c.dropUserV2AckCallback) + registry.RegisterAlterRoleV2AckCallback(c.alterRoleV2AckCallback) + registry.RegisterDropRoleV2AckCallback(c.dropRoleV2AckCallback) + registry.RegisterAlterUserRoleV2AckCallback(c.alterUserRoleV2AckCallback) + registry.RegisterDropUserRoleV2AckCallback(c.dropUserRoleV2AckCallback) + registry.RegisterAlterPrivilegeV2AckCallback(c.alterPrivilegeV2AckCallback) + registry.RegisterDropPrivilegeV2AckCallback(c.dropPrivilegeV2AckCallback) + registry.RegisterAlterPrivilegeGroupV2AckCallback(c.alterPrivilegeGroupV2AckCallback) + registry.RegisterDropPrivilegeGroupV2AckCallback(c.dropPrivilegeGroupV2AckCallback) + registry.RegisterRestoreRBACV2AckCallback(c.restoreRBACV2AckCallback) +} + +// DDLCallback is the callback of ddl. +type DDLCallback struct { + *Core +} + +// CacheExpirationsGetter is the getter of cache expirations. +type CacheExpirationsGetter interface { + GetCacheExpirations() *message.CacheExpirations +} + +// ExpireCaches handles the cache +func (c *DDLCallback) ExpireCaches(ctx context.Context, expirations any, timetick uint64) error { + var cacheExpirations *message.CacheExpirations + if g, ok := expirations.(CacheExpirationsGetter); ok { + cacheExpirations = g.GetCacheExpirations() + } else if g, ok := expirations.(*message.CacheExpirations); ok { + cacheExpirations = g + } else if g, ok := expirations.(*ce.CacheExpirationsBuilder); ok { + cacheExpirations = g.Build() + } else { + panic(fmt.Sprintf("invalid getter type: %T", expirations)) + } + for _, cacheExpiration := range cacheExpirations.CacheExpirations { + if err := c.expireCache(ctx, cacheExpiration, timetick); err != nil { + return err + } + } + return nil +} + +func (c *DDLCallback) expireCache(ctx context.Context, cacheExpiration *message.CacheExpiration, timetick uint64) error { + switch cacheExpiration.Cache.(type) { + case *messagespb.CacheExpiration_LegacyProxyCollectionMetaCache: + legacyProxyCollectionMetaCache := cacheExpiration.GetLegacyProxyCollectionMetaCache() + return c.Core.ExpireMetaCache(ctx, legacyProxyCollectionMetaCache.DbName, []string{legacyProxyCollectionMetaCache.CollectionName}, legacyProxyCollectionMetaCache.CollectionId, legacyProxyCollectionMetaCache.PartitionName, timetick, proxyutil.SetMsgType(legacyProxyCollectionMetaCache.MsgType)) + } + return nil +} + +// startBroadcastWithRBACLock starts a broadcast for rbac. +func startBroadcastWithRBACLock(ctx context.Context) (broadcaster.BroadcastAPI, error) { + api, err := broadcast.StartBroadcastWithResourceKeys(ctx, message.NewExclusivePrivilegeResourceKey()) + if err != nil { + return nil, errors.Wrap(err, "failed to start broadcast with rbac lock") + } + return api, nil +} diff --git a/internal/rootcoord/ddl_callbacks_rbac_credential.go b/internal/rootcoord/ddl_callbacks_rbac_credential.go new file mode 100644 index 0000000000..ff8a96d5cb --- /dev/null +++ b/internal/rootcoord/ddl_callbacks_rbac_credential.go @@ -0,0 +1,137 @@ +// Licensed to the LF AI & Data foundation 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 rootcoord + +import ( + "context" + "strings" + + "github.com/cockroachdb/errors" + + "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" + "github.com/milvus-io/milvus/internal/distributed/streaming" + "github.com/milvus-io/milvus/pkg/v2/proto/internalpb" + "github.com/milvus-io/milvus/pkg/v2/proto/proxypb" + "github.com/milvus-io/milvus/pkg/v2/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v2/util/typeutil" +) + +// broadcastAlterUserForCreateCredential broadcasts the alter user message for create credential. +func (c *Core) broadcastAlterUserForCreateCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error { + credInfo.Username = strings.TrimSpace(credInfo.Username) + broadcaster, err := startBroadcastWithRBACLock(ctx) + if err != nil { + return err + } + defer broadcaster.Close() + + if err := c.meta.CheckIfAddCredential(ctx, credInfo); err != nil { + return errors.Wrap(err, "failed to check if add credential") + } + + msg := message.NewAlterUserMessageBuilderV2(). + WithHeader(&message.AlterUserMessageHeader{ + UserEntity: &milvuspb.UserEntity{Name: credInfo.Username}, + }). + WithBody(&message.AlterUserMessageBody{ + CredentialInfo: credInfo, + }). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + _, err = broadcaster.Broadcast(ctx, msg) + return err +} + +// broadcastAlterUserForUpdateCredential broadcasts the alter user message for update credential. +func (c *Core) broadcastAlterUserForUpdateCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error { + credInfo.Username = strings.TrimSpace(credInfo.Username) + broadcaster, err := startBroadcastWithRBACLock(ctx) + if err != nil { + return err + } + defer broadcaster.Close() + + if err := c.meta.CheckIfUpdateCredential(ctx, credInfo); err != nil { + return errors.Wrap(err, "failed to check if update credential") + } + + msg := message.NewAlterUserMessageBuilderV2(). + WithHeader(&message.AlterUserMessageHeader{ + UserEntity: &milvuspb.UserEntity{Name: credInfo.Username}, + }). + WithBody(&message.AlterUserMessageBody{ + CredentialInfo: credInfo, + }). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + _, err = broadcaster.Broadcast(ctx, msg) + return err +} + +// alterUserV2AckCallback is the ack callback function for the AlterUserMessageV2 message. +func (c *DDLCallback) alterUserV2AckCallback(ctx context.Context, result message.BroadcastResultAlterUserMessageV2) error { + // insert to db + if err := c.meta.AlterCredential(ctx, result); err != nil { + return errors.Wrap(err, "failed to alter credential") + } + // update proxy's local cache + if err := c.UpdateCredCache(ctx, result.Message.MustBody().CredentialInfo); err != nil { + return errors.Wrap(err, "failed to update cred cache") + } + return nil +} + +// broadcastDropUserForDeleteCredential broadcasts the drop user message for delete credential. +func (c *Core) broadcastDropUserForDeleteCredential(ctx context.Context, in *milvuspb.DeleteCredentialRequest) error { + in.Username = strings.TrimSpace(in.Username) + broadcaster, err := startBroadcastWithRBACLock(ctx) + if err != nil { + return err + } + defer broadcaster.Close() + + if err := c.meta.CheckIfDeleteCredential(ctx, in); err != nil { + return errors.Wrap(err, "failed to check if delete credential") + } + + msg := message.NewDropUserMessageBuilderV2(). + WithHeader(&message.DropUserMessageHeader{ + UserName: in.Username, + }). + WithBody(&message.DropUserMessageBody{}). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + _, err = broadcaster.Broadcast(ctx, msg) + return err +} + +// dropUserV2AckCallback is the ack callback function for the DeleteCredential message +func (c *DDLCallback) dropUserV2AckCallback(ctx context.Context, result message.BroadcastResultDropUserMessageV2) error { + if err := c.meta.DeleteCredential(ctx, result); err != nil { + return errors.Wrap(err, "failed to delete credential") + } + if err := c.ExpireCredCache(ctx, result.Message.Header().UserName); err != nil { + return errors.Wrap(err, "failed to expire cred cache") + } + if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ + OpType: int32(typeutil.CacheDeleteUser), + OpKey: result.Message.Header().UserName, + }); err != nil { + return errors.Wrap(err, "failed to refresh policy info cache") + } + return nil +} diff --git a/internal/rootcoord/ddl_callbacks_rbac_credential_test.go b/internal/rootcoord/ddl_callbacks_rbac_credential_test.go new file mode 100644 index 0000000000..8ef8fa4b3c --- /dev/null +++ b/internal/rootcoord/ddl_callbacks_rbac_credential_test.go @@ -0,0 +1,106 @@ +// Licensed to the LF AI & Data foundation 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 rootcoord + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" + etcdkv "github.com/milvus-io/milvus/internal/kv/etcd" + "github.com/milvus-io/milvus/internal/metastore/kv/rootcoord" + "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/registry" + kvfactory "github.com/milvus-io/milvus/internal/util/dependency/kv" + "github.com/milvus-io/milvus/pkg/v2/proto/internalpb" + "github.com/milvus-io/milvus/pkg/v2/proto/rootcoordpb" + "github.com/milvus-io/milvus/pkg/v2/util/funcutil" + "github.com/milvus-io/milvus/pkg/v2/util/merr" +) + +func TestDDLCallbacksRBACCredential(t *testing.T) { + initStreamingSystem() + + kv, _ := kvfactory.GetEtcdAndPath() + path := funcutil.RandomString(10) + catalogKV := etcdkv.NewEtcdKV(kv, path) + + testUserName := "user" + funcutil.RandomString(10) + + core := newTestCore(withHealthyCode(), + withMeta(&MetaTable{catalog: rootcoord.NewCatalog(catalogKV, nil)}), + withValidProxyManager(), + ) + registry.ResetRegistration() + RegisterDDLCallbacks(core) + + // Delete a not existed credential should succeed + status, err := core.DeleteCredential(context.Background(), &milvuspb.DeleteCredentialRequest{ + Username: testUserName, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + // Update a not existed credential should return error. + status, err = core.UpdateCredential(context.Background(), &internalpb.CredentialInfo{ + Username: testUserName, + EncryptedPassword: "123456", + }) + require.Error(t, merr.CheckRPCCall(status, err)) + + // Create a new credential. + status, err = core.CreateCredential(context.Background(), &internalpb.CredentialInfo{ + Username: testUserName, + EncryptedPassword: "123456", + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + getCredentialResp, err := core.GetCredential(context.Background(), &rootcoordpb.GetCredentialRequest{ + Username: testUserName, + }) + require.NoError(t, merr.CheckRPCCall(getCredentialResp.Status, err)) + assert.Equal(t, "123456", getCredentialResp.Password) + + // Create a new credential with same username should return error. + status, err = core.CreateCredential(context.Background(), &internalpb.CredentialInfo{ + Username: testUserName, + EncryptedPassword: "123456", + }) + require.Error(t, merr.CheckRPCCall(status, err)) + + // Update the created credential. + status, err = core.UpdateCredential(context.Background(), &internalpb.CredentialInfo{ + Username: testUserName, + EncryptedPassword: "1234567", + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + getCredentialResp, err = core.GetCredential(context.Background(), &rootcoordpb.GetCredentialRequest{ + Username: testUserName, + }) + require.NoError(t, merr.CheckRPCCall(getCredentialResp.Status, err)) + assert.Equal(t, "1234567", getCredentialResp.Password) + + // Delete the created credential. + status, err = core.DeleteCredential(context.Background(), &milvuspb.DeleteCredentialRequest{ + Username: testUserName, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + getCredentialResp, err = core.GetCredential(context.Background(), &rootcoordpb.GetCredentialRequest{ + Username: testUserName, + }) + require.Error(t, merr.CheckRPCCall(getCredentialResp.Status, err)) +} diff --git a/internal/rootcoord/ddl_callbacks_rbac_privilege.go b/internal/rootcoord/ddl_callbacks_rbac_privilege.go new file mode 100644 index 0000000000..5defc9c4b9 --- /dev/null +++ b/internal/rootcoord/ddl_callbacks_rbac_privilege.go @@ -0,0 +1,199 @@ +// Licensed to the LF AI & Data foundation 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 rootcoord + +import ( + "context" + "strings" + + "github.com/cockroachdb/errors" + + "github.com/milvus-io/milvus-proto/go-api/v2/commonpb" + "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" + "github.com/milvus-io/milvus/internal/distributed/streaming" + "github.com/milvus-io/milvus/pkg/v2/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v2/util" +) + +func (c *Core) broadcastOperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivilegeRequest) error { + broadcaster, err := startBroadcastWithRBACLock(ctx) + if err != nil { + return err + } + defer broadcaster.Close() + + if err := c.operatePrivilegeCommonCheck(ctx, in); err != nil { + return errors.Wrap(err, "failed to operate privilege common check") + } + privName := in.Entity.Grantor.Privilege.Name + switch in.Version { + case "v2": + if err := c.isValidPrivilegeV2(ctx, privName); err != nil { + return err + } + if err := c.validatePrivilegeGroupParams(ctx, privName, in.Entity.DbName, in.Entity.ObjectName); err != nil { + return err + } + // set up object type for metastore, to be compatible with v1 version + in.Entity.Object.Name = util.GetObjectType(privName) + default: + if err := c.isValidPrivilege(ctx, privName, in.Entity.Object.Name); err != nil { + return err + } + // set up object name if it is global object type and not built in privilege group + if in.Entity.Object.Name == commonpb.ObjectType_Global.String() && !util.IsBuiltinPrivilegeGroup(in.Entity.Grantor.Privilege.Name) { + in.Entity.ObjectName = util.AnyWord + } + } + + var msg message.BroadcastMutableMessage + switch in.Type { + case milvuspb.OperatePrivilegeType_Grant: + msg = message.NewAlterPrivilegeMessageBuilderV2(). + WithHeader(&message.AlterPrivilegeMessageHeader{ + Entity: in.Entity, + }). + WithBody(&message.AlterPrivilegeMessageBody{}). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + case milvuspb.OperatePrivilegeType_Revoke: + msg = message.NewDropPrivilegeMessageBuilderV2(). + WithHeader(&message.DropPrivilegeMessageHeader{ + Entity: in.Entity, + }). + WithBody(&message.DropPrivilegeMessageBody{}). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + default: + return errors.New("invalid operate privilege type") + } + _, err = broadcaster.Broadcast(ctx, msg) + return err +} + +func (c *DDLCallback) alterPrivilegeV2AckCallback(ctx context.Context, result message.BroadcastResultAlterPrivilegeMessageV2) error { + return executeOperatePrivilegeTaskSteps(ctx, c.Core, result.Message.Header().Entity, milvuspb.OperatePrivilegeType_Grant) +} + +func (c *DDLCallback) dropPrivilegeV2AckCallback(ctx context.Context, result message.BroadcastResultDropPrivilegeMessageV2) error { + return executeOperatePrivilegeTaskSteps(ctx, c.Core, result.Message.Header().Entity, milvuspb.OperatePrivilegeType_Revoke) +} + +func (c *Core) broadcastCreatePrivilegeGroup(ctx context.Context, in *milvuspb.CreatePrivilegeGroupRequest) error { + in.GroupName = strings.TrimSpace(in.GroupName) + broadcaster, err := startBroadcastWithRBACLock(ctx) + if err != nil { + return err + } + defer broadcaster.Close() + + if err := c.meta.CheckIfPrivilegeGroupCreatable(ctx, in); err != nil { + return errors.Wrap(err, "failed to check if privilege group creatable") + } + + msg := message.NewAlterPrivilegeGroupMessageBuilderV2(). + WithHeader(&message.AlterPrivilegeGroupMessageHeader{ + PrivilegeGroupInfo: &milvuspb.PrivilegeGroupInfo{ + GroupName: in.GroupName, + }, + }). + WithBody(&message.AlterPrivilegeGroupMessageBody{}). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + _, err = broadcaster.Broadcast(ctx, msg) + return err +} + +func (c *Core) broadcastOperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePrivilegeGroupRequest) error { + broadcaster, err := startBroadcastWithRBACLock(ctx) + if err != nil { + return err + } + defer broadcaster.Close() + + if err := c.meta.CheckIfPrivilegeGroupAlterable(ctx, in); err != nil { + return errors.Wrap(err, "failed to check if privilege group alterable") + } + + var msg message.BroadcastMutableMessage + switch in.Type { + case milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup: + msg = message.NewAlterPrivilegeGroupMessageBuilderV2(). + WithHeader(&message.AlterPrivilegeGroupMessageHeader{ + PrivilegeGroupInfo: &milvuspb.PrivilegeGroupInfo{ + GroupName: in.GroupName, + Privileges: in.Privileges, + }, + }). + WithBody(&message.AlterPrivilegeGroupMessageBody{}). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + case milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup: + msg = message.NewDropPrivilegeGroupMessageBuilderV2(). + WithHeader(&message.DropPrivilegeGroupMessageHeader{ + PrivilegeGroupInfo: &milvuspb.PrivilegeGroupInfo{ + GroupName: in.GroupName, + Privileges: in.Privileges, + }, + }). + WithBody(&message.DropPrivilegeGroupMessageBody{}). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + default: + return errors.New("invalid operate privilege group type") + } + _, err = broadcaster.Broadcast(ctx, msg) + return err +} + +func (c *DDLCallback) alterPrivilegeGroupV2AckCallback(ctx context.Context, result message.BroadcastResultAlterPrivilegeGroupMessageV2) error { + if len(result.Message.Header().PrivilegeGroupInfo.Privileges) == 0 { + return c.meta.CreatePrivilegeGroup(ctx, result.Message.Header().PrivilegeGroupInfo.GroupName) + } + return executeOperatePrivilegeGroupTaskSteps(ctx, c.Core, result.Message.Header().PrivilegeGroupInfo, milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup) +} + +func (c *Core) broadcastDropPrivilegeGroup(ctx context.Context, in *milvuspb.DropPrivilegeGroupRequest) error { + broadcaster, err := startBroadcastWithRBACLock(ctx) + if err != nil { + return err + } + defer broadcaster.Close() + + if err := c.meta.CheckIfPrivilegeGroupDropable(ctx, in); err != nil { + return errors.Wrap(err, "failed to check if privilege group dropable") + } + + msg := message.NewDropPrivilegeGroupMessageBuilderV2(). + WithHeader(&message.DropPrivilegeGroupMessageHeader{ + PrivilegeGroupInfo: &milvuspb.PrivilegeGroupInfo{ + GroupName: in.GroupName, + }, + }). + WithBody(&message.DropPrivilegeGroupMessageBody{}). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + _, err = broadcaster.Broadcast(ctx, msg) + return err +} + +func (c *DDLCallback) dropPrivilegeGroupV2AckCallback(ctx context.Context, result message.BroadcastResultDropPrivilegeGroupMessageV2) error { + if len(result.Message.Header().PrivilegeGroupInfo.Privileges) == 0 { + return c.meta.DropPrivilegeGroup(ctx, result.Message.Header().PrivilegeGroupInfo.GroupName) + } + return executeOperatePrivilegeGroupTaskSteps(ctx, c.Core, result.Message.Header().PrivilegeGroupInfo, milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup) +} diff --git a/internal/rootcoord/ddl_callbacks_rbac_privilege_test.go b/internal/rootcoord/ddl_callbacks_rbac_privilege_test.go new file mode 100644 index 0000000000..4900860ca0 --- /dev/null +++ b/internal/rootcoord/ddl_callbacks_rbac_privilege_test.go @@ -0,0 +1,211 @@ +// Licensed to the LF AI & Data foundation 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 rootcoord + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" + etcdkv "github.com/milvus-io/milvus/internal/kv/etcd" + "github.com/milvus-io/milvus/internal/metastore/kv/rootcoord" + "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/registry" + kvfactory "github.com/milvus-io/milvus/internal/util/dependency/kv" + "github.com/milvus-io/milvus/pkg/v2/proto/internalpb" + "github.com/milvus-io/milvus/pkg/v2/util/funcutil" + "github.com/milvus-io/milvus/pkg/v2/util/merr" +) + +func TestDDLCallbacksRBACPrivilege(t *testing.T) { + initStreamingSystem() + + kv, _ := kvfactory.GetEtcdAndPath() + path := funcutil.RandomString(10) + catalogKV := etcdkv.NewEtcdKV(kv, path) + + core := newTestCore(withHealthyCode(), + withMeta(&MetaTable{catalog: rootcoord.NewCatalog(catalogKV, nil)}), + withValidProxyManager(), + ) + registry.ResetRegistration() + RegisterDDLCallbacks(core) + + // Create a new role. + targetRoleName := "newRole" + status, err := core.CreateRole(context.Background(), &milvuspb.CreateRoleRequest{ + Entity: &milvuspb.RoleEntity{ + Name: targetRoleName, + }, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + targetUserName := "newUser" + status, err = core.CreateCredential(context.Background(), &internalpb.CredentialInfo{ + Username: targetUserName, + EncryptedPassword: "123456", + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + // Drop not existed privilege should return error. + status, err = core.OperatePrivilege(context.Background(), &milvuspb.OperatePrivilegeRequest{ + Type: milvuspb.OperatePrivilegeType_Revoke, + Entity: &milvuspb.GrantEntity{ + Role: &milvuspb.RoleEntity{ + Name: targetRoleName, + }, + Grantor: &milvuspb.GrantorEntity{ + Privilege: &milvuspb.PrivilegeEntity{ + Name: "not existed", + }, + }, + }, + }) + require.Error(t, merr.CheckRPCCall(status, err)) + + entity := &milvuspb.GrantEntity{ + Role: &milvuspb.RoleEntity{ + Name: targetRoleName, + }, + Object: &milvuspb.ObjectEntity{ + Name: "Global", + }, + ObjectName: "*", + Grantor: &milvuspb.GrantorEntity{ + Privilege: &milvuspb.PrivilegeEntity{ + Name: "DescribeCollection", + }, + User: &milvuspb.UserEntity{ + Name: targetUserName, + }, + }, + } + + // Grant and revoke with v2 version + status, err = core.OperatePrivilege(context.Background(), &milvuspb.OperatePrivilegeRequest{ + Type: milvuspb.OperatePrivilegeType_Grant, + Entity: entity, + Version: "v2", + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + selectGrantResp, err := core.SelectGrant(context.Background(), &milvuspb.SelectGrantRequest{ + Entity: entity, + }) + require.NoError(t, merr.CheckRPCCall(selectGrantResp, err)) + require.Equal(t, 1, len(selectGrantResp.Entities)) + + status, err = core.OperatePrivilege(context.Background(), &milvuspb.OperatePrivilegeRequest{ + Type: milvuspb.OperatePrivilegeType_Revoke, + Entity: entity, + Version: "v2", + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + selectGrantResp, err = core.SelectGrant(context.Background(), &milvuspb.SelectGrantRequest{ + Entity: entity, + }) + require.NoError(t, merr.CheckRPCCall(selectGrantResp, err)) + require.Equal(t, 0, len(selectGrantResp.Entities)) + + // Grant and revoke with v1 version + status, err = core.OperatePrivilege(context.Background(), &milvuspb.OperatePrivilegeRequest{ + Type: milvuspb.OperatePrivilegeType_Grant, + Entity: entity, + Version: "v1", + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + selectGrantResp, err = core.SelectGrant(context.Background(), &milvuspb.SelectGrantRequest{ + Entity: entity, + }) + require.NoError(t, merr.CheckRPCCall(selectGrantResp, err)) + require.Equal(t, 1, len(selectGrantResp.Entities)) + + status, err = core.OperatePrivilege(context.Background(), &milvuspb.OperatePrivilegeRequest{ + Type: milvuspb.OperatePrivilegeType_Revoke, + Entity: entity, + Version: "v1", + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + selectGrantResp, err = core.SelectGrant(context.Background(), &milvuspb.SelectGrantRequest{ + Entity: entity, + }) + require.NoError(t, merr.CheckRPCCall(selectGrantResp, err)) + require.Equal(t, 0, len(selectGrantResp.Entities)) + + // Grant and try drop role should return error + status, err = core.OperatePrivilege(context.Background(), &milvuspb.OperatePrivilegeRequest{ + Type: milvuspb.OperatePrivilegeType_Grant, + Entity: entity, + Version: "v1", + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + status, err = core.DropRole(context.Background(), &milvuspb.DropRoleRequest{ + RoleName: targetRoleName, + }) + require.Error(t, merr.CheckRPCCall(status, err)) +} + +func TestDDLCallbacksRBACPrivilegeGroup(t *testing.T) { + initStreamingSystem() + + kv, _ := kvfactory.GetEtcdAndPath() + path := funcutil.RandomString(10) + catalogKV := etcdkv.NewEtcdKV(kv, path) + + core := newTestCore(withHealthyCode(), + withMeta(&MetaTable{catalog: rootcoord.NewCatalog(catalogKV, nil)}), + withValidProxyManager(), + ) + registry.ResetRegistration() + RegisterDDLCallbacks(core) + + groupName := "group1" + status, err := core.CreatePrivilegeGroup(context.Background(), &milvuspb.CreatePrivilegeGroupRequest{ + GroupName: groupName, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + status, err = core.OperatePrivilegeGroup(context.Background(), &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: groupName, + Type: milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup, + Privileges: []*milvuspb.PrivilegeEntity{{Name: "Query"}}, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + status, err = core.OperatePrivilegeGroup(context.Background(), &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: groupName, + Type: milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup, + Privileges: []*milvuspb.PrivilegeEntity{{Name: "Query"}}, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + status, err = core.OperatePrivilegeGroup(context.Background(), &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: groupName, + Type: milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup, + Privileges: []*milvuspb.PrivilegeEntity{{Name: "Query"}}, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + status, err = core.DropPrivilegeGroup(context.Background(), &milvuspb.DropPrivilegeGroupRequest{ + GroupName: groupName, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) +} diff --git a/internal/rootcoord/ddl_callbacks_rbac_restore.go b/internal/rootcoord/ddl_callbacks_rbac_restore.go new file mode 100644 index 0000000000..ac91f97724 --- /dev/null +++ b/internal/rootcoord/ddl_callbacks_rbac_restore.go @@ -0,0 +1,65 @@ +// Licensed to the LF AI & Data foundation 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 rootcoord + +import ( + "context" + + "github.com/cockroachdb/errors" + + "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" + "github.com/milvus-io/milvus/internal/distributed/streaming" + "github.com/milvus-io/milvus/pkg/v2/proto/proxypb" + "github.com/milvus-io/milvus/pkg/v2/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v2/util" + "github.com/milvus-io/milvus/pkg/v2/util/typeutil" +) + +func (c *Core) broadcastRestoreRBACV2(ctx context.Context, req *milvuspb.RestoreRBACMetaRequest) error { + broadcaster, err := startBroadcastWithRBACLock(ctx) + if err != nil { + return err + } + defer broadcaster.Close() + + if err := c.meta.CheckIfRBACRestorable(ctx, req); err != nil { + return errors.Wrap(err, "failed to check if rbac restorable") + } + + msg := message.NewRestoreRBACMessageBuilderV2(). + WithHeader(&message.RestoreRBACMessageHeader{}). + WithBody(&message.RestoreRBACMessageBody{ + RbacMeta: req.GetRBACMeta(), + }). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + _, err = broadcaster.Broadcast(ctx, msg) + return err +} + +func (c *DDLCallback) restoreRBACV2AckCallback(ctx context.Context, result message.BroadcastResultRestoreRBACMessageV2) error { + meta := result.Message.MustBody().RbacMeta + if err := c.meta.RestoreRBAC(ctx, util.DefaultTenant, meta); err != nil { + return errors.Wrap(err, "failed to restore rbac meta data") + } + if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ + OpType: int32(typeutil.CacheRefresh), + }); err != nil { + return errors.Wrap(err, "failed to refresh policy info cache") + } + return nil +} diff --git a/internal/rootcoord/ddl_callbacks_rbac_restore_test.go b/internal/rootcoord/ddl_callbacks_rbac_restore_test.go new file mode 100644 index 0000000000..7072817cf6 --- /dev/null +++ b/internal/rootcoord/ddl_callbacks_rbac_restore_test.go @@ -0,0 +1,177 @@ +// Licensed to the LF AI & Data foundation 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 rootcoord + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" + etcdkv "github.com/milvus-io/milvus/internal/kv/etcd" + "github.com/milvus-io/milvus/internal/metastore/kv/rootcoord" + "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/registry" + kvfactory "github.com/milvus-io/milvus/internal/util/dependency/kv" + "github.com/milvus-io/milvus/pkg/v2/util" + "github.com/milvus-io/milvus/pkg/v2/util/funcutil" + "github.com/milvus-io/milvus/pkg/v2/util/merr" +) + +func TestDDLCallbacksRBACRestore(t *testing.T) { + initStreamingSystem() + kv, _ := kvfactory.GetEtcdAndPath() + path := funcutil.RandomString(10) + catalogKV := etcdkv.NewEtcdKV(kv, path) + + core := newTestCore(withHealthyCode(), + withMeta(&MetaTable{catalog: rootcoord.NewCatalog(catalogKV, nil)}), + withValidProxyManager(), + ) + registry.ResetRegistration() + RegisterDDLCallbacks(core) + + ctx := context.Background() + + rbacMeta := &milvuspb.RBACMeta{ + Users: []*milvuspb.UserInfo{ + { + User: "user1", + Password: "passwd", + Roles: []*milvuspb.RoleEntity{ + { + Name: "role1", + }, + }, + }, + }, + Roles: []*milvuspb.RoleEntity{ + { + Name: "role1", + }, + }, + + Grants: []*milvuspb.GrantEntity{ + { + Role: &milvuspb.RoleEntity{Name: "role1"}, + Object: &milvuspb.ObjectEntity{Name: "obj1"}, + ObjectName: "obj_name1", + DbName: util.DefaultDBName, + Grantor: &milvuspb.GrantorEntity{ + User: &milvuspb.UserEntity{Name: "user1"}, + Privilege: &milvuspb.PrivilegeEntity{Name: "Load"}, + }, + }, + }, + + PrivilegeGroups: []*milvuspb.PrivilegeGroupInfo{ + { + GroupName: "custom_group", + Privileges: []*milvuspb.PrivilegeEntity{{Name: "CreateCollection"}}, + }, + }, + } + // test restore success + status, err := core.RestoreRBAC(ctx, &milvuspb.RestoreRBACMetaRequest{ + RBACMeta: rbacMeta, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + status, err = core.RestoreRBAC(ctx, &milvuspb.RestoreRBACMetaRequest{ + RBACMeta: rbacMeta, + }) + require.Error(t, merr.CheckRPCCall(status, err)) + + // check user + users, err := core.meta.ListCredentialUsernames(ctx) + assert.NoError(t, err) + assert.Len(t, users.Usernames, 1) + assert.Equal(t, "user1", users.Usernames[0]) + // check grant + userRoles, err := core.meta.ListUserRole(ctx, util.DefaultTenant) + assert.NoError(t, err) + assert.Len(t, userRoles, 1) + assert.Equal(t, "user1/role1", userRoles[0]) + policies, err := core.meta.SelectGrant(ctx, util.DefaultTenant, rbacMeta.Grants[0]) + assert.NoError(t, err) + assert.Len(t, policies, 1) + assert.Equal(t, "obj_name1", policies[0].ObjectName) + assert.Equal(t, "role1", policies[0].Role.Name) + assert.Equal(t, "user1", policies[0].Grantor.User.Name) + assert.Equal(t, "Load", policies[0].Grantor.Privilege.Name) + // check privilege group + privGroups, err := core.meta.ListPrivilegeGroups(ctx) + assert.NoError(t, err) + assert.Len(t, privGroups, 1) + assert.Equal(t, "custom_group", privGroups[0].GroupName) + assert.Equal(t, "CreateCollection", privGroups[0].Privileges[0].Name) + + rbacMeta2 := &milvuspb.RBACMeta{ + Users: []*milvuspb.UserInfo{ + { + User: "user2", + Password: "passwd", + Roles: []*milvuspb.RoleEntity{ + { + Name: "role2", + }, + }, + }, + { + User: "user1", + Password: "passwd", + Roles: []*milvuspb.RoleEntity{ + { + Name: "role2", + }, + }, + }, + }, + Roles: []*milvuspb.RoleEntity{ + { + Name: "role2", + }, + }, + + Grants: []*milvuspb.GrantEntity{ + { + Role: &milvuspb.RoleEntity{Name: "role2"}, + Object: &milvuspb.ObjectEntity{Name: "obj2"}, + ObjectName: "obj_name2", + DbName: util.DefaultDBName, + Grantor: &milvuspb.GrantorEntity{ + User: &milvuspb.UserEntity{Name: "user2"}, + Privilege: &milvuspb.PrivilegeEntity{Name: "Load"}, + }, + }, + }, + + PrivilegeGroups: []*milvuspb.PrivilegeGroupInfo{ + { + GroupName: "custom_group2", + Privileges: []*milvuspb.PrivilegeEntity{{Name: "DropCollection"}}, + }, + }, + } + + // test restore failed and roll back + status, err = core.RestoreRBAC(ctx, &milvuspb.RestoreRBACMetaRequest{ + RBACMeta: rbacMeta2, + }) + require.Error(t, merr.CheckRPCCall(status, err)) +} diff --git a/internal/rootcoord/ddl_callbacks_rbac_role.go b/internal/rootcoord/ddl_callbacks_rbac_role.go new file mode 100644 index 0000000000..b33e792a39 --- /dev/null +++ b/internal/rootcoord/ddl_callbacks_rbac_role.go @@ -0,0 +1,170 @@ +// Licensed to the LF AI & Data foundation 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 rootcoord + +import ( + "context" + + "github.com/cockroachdb/errors" + + "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" + "github.com/milvus-io/milvus/internal/distributed/streaming" + "github.com/milvus-io/milvus/pkg/v2/proto/proxypb" + "github.com/milvus-io/milvus/pkg/v2/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v2/util" + "github.com/milvus-io/milvus/pkg/v2/util/funcutil" + "github.com/milvus-io/milvus/pkg/v2/util/typeutil" +) + +func (c *Core) broadcastCreateRole(ctx context.Context, in *milvuspb.CreateRoleRequest) error { + broadcaster, err := startBroadcastWithRBACLock(ctx) + if err != nil { + return err + } + defer broadcaster.Close() + + if err := c.meta.CheckIfCreateRole(ctx, in); err != nil { + return errors.Wrap(err, "failed to check if create role") + } + + msg := message.NewAlterRoleMessageBuilderV2(). + WithHeader(&message.AlterRoleMessageHeader{ + RoleEntity: in.GetEntity(), + }). + WithBody(&message.AlterRoleMessageBody{}). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + _, err = broadcaster.Broadcast(ctx, msg) + return err +} + +// alterRoleV2AckCallback is the ack callback function for the AlterRoleMessageV2 message. +func (c *DDLCallback) alterRoleV2AckCallback(ctx context.Context, result message.BroadcastResultAlterRoleMessageV2) error { + return c.meta.CreateRole(ctx, util.DefaultTenant, result.Message.Header().RoleEntity) +} + +func (c *Core) broadcastDropRole(ctx context.Context, in *milvuspb.DropRoleRequest) error { + broadcaster, err := startBroadcastWithRBACLock(ctx) + if err != nil { + return err + } + defer broadcaster.Close() + + if err := c.meta.CheckIfDropRole(ctx, in); err != nil { + return errors.Wrap(err, "failed to check if drop role") + } + + msg := message.NewDropRoleMessageBuilderV2(). + WithHeader(&message.DropRoleMessageHeader{ + RoleName: in.RoleName, + }). + WithBody(&message.DropRoleMessageBody{}). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + _, err = broadcaster.Broadcast(ctx, msg) + return err +} + +// dropRoleV2AckCallback is the ack callback function for the DropRoleMessageV2 message. +func (c *DDLCallback) dropRoleV2AckCallback(ctx context.Context, result message.BroadcastResultDropRoleMessageV2) error { + // There should always be only one message in the msgs slice. + msg := result.Message + err := c.meta.DropRole(ctx, util.DefaultTenant, msg.Header().RoleName) + if err != nil { + return errors.Wrap(err, "failed to drop role") + } + if err := c.meta.DropGrant(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: msg.Header().RoleName}); err != nil { + return errors.Wrap(err, "failed to drop grant") + } + if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ + OpType: int32(typeutil.CacheDropRole), + OpKey: msg.Header().RoleName, + }); err != nil { + return errors.Wrap(err, "failed to refresh policy info cache") + } + return nil +} + +func (c *Core) broadcastOperateUserRole(ctx context.Context, in *milvuspb.OperateUserRoleRequest) error { + broadcaster, err := startBroadcastWithRBACLock(ctx) + if err != nil { + return err + } + defer broadcaster.Close() + + if err := c.meta.CheckIfOperateUserRole(ctx, in); err != nil { + return errors.Wrap(err, "failed to check if operate user role") + } + + var msg message.BroadcastMutableMessage + switch in.Type { + case milvuspb.OperateUserRoleType_AddUserToRole: + msg = message.NewAlterUserRoleMessageBuilderV2(). + WithHeader(&message.AlterUserRoleMessageHeader{ + RoleBinding: &message.RoleBinding{ + UserEntity: &milvuspb.UserEntity{Name: in.Username}, + RoleEntity: &milvuspb.RoleEntity{Name: in.RoleName}, + }, + }). + WithBody(&message.AlterUserRoleMessageBody{}). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + case milvuspb.OperateUserRoleType_RemoveUserFromRole: + msg = message.NewDropUserRoleMessageBuilderV2(). + WithHeader(&message.DropUserRoleMessageHeader{ + RoleBinding: &message.RoleBinding{ + UserEntity: &milvuspb.UserEntity{Name: in.Username}, + RoleEntity: &milvuspb.RoleEntity{Name: in.RoleName}, + }, + }). + WithBody(&message.DropUserRoleMessageBody{}). + WithBroadcast([]string{streaming.WAL().ControlChannel()}). + MustBuildBroadcast() + default: + return errors.New("invalid operate user role type") + } + _, err = broadcaster.Broadcast(ctx, msg) + return err +} + +func (c *DDLCallback) alterUserRoleV2AckCallback(ctx context.Context, result message.BroadcastResultAlterUserRoleMessageV2) error { + header := result.Message.Header() + if err := c.meta.OperateUserRole(ctx, util.DefaultTenant, header.RoleBinding.UserEntity, header.RoleBinding.RoleEntity, milvuspb.OperateUserRoleType_AddUserToRole); err != nil { + return errors.Wrap(err, "failed to operate user role") + } + if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ + OpType: int32(typeutil.CacheAddUserToRole), + OpKey: funcutil.EncodeUserRoleCache(header.RoleBinding.UserEntity.Name, header.RoleBinding.RoleEntity.Name), + }); err != nil { + return errors.Wrap(err, "failed to refresh policy info cache") + } + return nil +} + +func (c *DDLCallback) dropUserRoleV2AckCallback(ctx context.Context, result message.BroadcastResultDropUserRoleMessageV2) error { + header := result.Message.Header() + if err := c.meta.OperateUserRole(ctx, util.DefaultTenant, header.RoleBinding.UserEntity, header.RoleBinding.RoleEntity, milvuspb.OperateUserRoleType_RemoveUserFromRole); err != nil { + return errors.Wrap(err, "failed to operate user role") + } + if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ + OpType: int32(typeutil.CacheRemoveUserFromRole), + OpKey: funcutil.EncodeUserRoleCache(header.RoleBinding.UserEntity.Name, header.RoleBinding.RoleEntity.Name), + }); err != nil { + return errors.Wrap(err, "failed to refresh policy info cache") + } + return nil +} diff --git a/internal/rootcoord/ddl_callbacks_rbac_role_test.go b/internal/rootcoord/ddl_callbacks_rbac_role_test.go new file mode 100644 index 0000000000..6b85017d3f --- /dev/null +++ b/internal/rootcoord/ddl_callbacks_rbac_role_test.go @@ -0,0 +1,155 @@ +// Licensed to the LF AI & Data foundation 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 rootcoord + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" + etcdkv "github.com/milvus-io/milvus/internal/kv/etcd" + "github.com/milvus-io/milvus/internal/metastore/kv/rootcoord" + "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/registry" + kvfactory "github.com/milvus-io/milvus/internal/util/dependency/kv" + "github.com/milvus-io/milvus/pkg/v2/proto/internalpb" + "github.com/milvus-io/milvus/pkg/v2/util" + "github.com/milvus-io/milvus/pkg/v2/util/funcutil" + "github.com/milvus-io/milvus/pkg/v2/util/merr" + "github.com/milvus-io/milvus/pkg/v2/util/paramtable" +) + +func TestDDLCallbacksRBACRole(t *testing.T) { + initStreamingSystem() + + kv, _ := kvfactory.GetEtcdAndPath() + path := funcutil.RandomString(10) + catalogKV := etcdkv.NewEtcdKV(kv, path) + + core := newTestCore(withHealthyCode(), + withMeta(&MetaTable{catalog: rootcoord.NewCatalog(catalogKV, nil)}), + withValidProxyManager(), + ) + registry.ResetRegistration() + RegisterDDLCallbacks(core) + + // Test drop builtin role should return error + roleDbAdmin := "db_admin" + paramtable.Init() + paramtable.Get().Save(paramtable.Get().RoleCfg.Enabled.Key, "true") + paramtable.Get().Save(paramtable.Get().RoleCfg.Roles.Key, `{"`+roleDbAdmin+`": {"privileges": [{"object_type": "Global", "object_name": "*", "privilege": "CreateCollection", "db_name": "*"}]}}`) + err := core.initBuiltinRoles(context.Background()) + assert.Equal(t, nil, err) + assert.True(t, util.IsBuiltinRole(roleDbAdmin)) + assert.False(t, util.IsBuiltinRole(util.RoleAdmin)) + resp, err := core.DropRole(context.Background(), &milvuspb.DropRoleRequest{RoleName: roleDbAdmin}) + assert.Equal(t, nil, err) + assert.Equal(t, int32(1401), resp.Code) // merr.ErrPrivilegeNotPermitted + + // Create a new credential. + testUserName := "user" + funcutil.RandomString(10) + status, err := core.CreateCredential(context.Background(), &internalpb.CredentialInfo{ + Username: testUserName, + EncryptedPassword: "123456", + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + + testRoleName := "role" + funcutil.RandomString(10) + + // Drop a not existed role should return error. + status, err = core.DropRole(context.Background(), &milvuspb.DropRoleRequest{ + RoleName: testRoleName, + }) + require.Error(t, merr.CheckRPCCall(status, err)) + + // Operate a not existed role should return error. + status, err = core.OperateUserRole(context.Background(), &milvuspb.OperateUserRoleRequest{ + RoleName: testRoleName, + Username: testUserName, + Type: milvuspb.OperateUserRoleType_AddUserToRole, + }) + require.Error(t, merr.CheckRPCCall(status, err)) + + // Create a new role. + status, err = core.CreateRole(context.Background(), &milvuspb.CreateRoleRequest{ + Entity: &milvuspb.RoleEntity{ + Name: testRoleName, + }, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + selectRoleResp, err := core.SelectRole(context.Background(), &milvuspb.SelectRoleRequest{ + Role: &milvuspb.RoleEntity{ + Name: testRoleName, + }, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + assert.Equal(t, 1, len(selectRoleResp.Results)) + assert.Equal(t, testRoleName, selectRoleResp.Results[0].Role.GetName()) + + // Add user to role. + status, err = core.OperateUserRole(context.Background(), &milvuspb.OperateUserRoleRequest{ + RoleName: testRoleName, + Username: testUserName, + Type: milvuspb.OperateUserRoleType_AddUserToRole, + }) + assert.NoError(t, merr.CheckRPCCall(status, err)) + selectRoleResp, err = core.SelectRole(context.Background(), &milvuspb.SelectRoleRequest{ + Role: &milvuspb.RoleEntity{ + Name: testRoleName, + }, + IncludeUserInfo: true, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + assert.Equal(t, 1, len(selectRoleResp.Results)) + assert.Equal(t, testRoleName, selectRoleResp.Results[0].Role.GetName()) + assert.Equal(t, 1, len(selectRoleResp.Results[0].Users)) + assert.Equal(t, testUserName, selectRoleResp.Results[0].Users[0].GetName()) + + // Remove a user from role. + status, err = core.OperateUserRole(context.Background(), &milvuspb.OperateUserRoleRequest{ + RoleName: testRoleName, + Username: testUserName, + Type: milvuspb.OperateUserRoleType_RemoveUserFromRole, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + selectRoleResp, err = core.SelectRole(context.Background(), &milvuspb.SelectRoleRequest{ + Role: &milvuspb.RoleEntity{ + Name: testRoleName, + }, + IncludeUserInfo: true, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + assert.Equal(t, 1, len(selectRoleResp.Results)) + assert.Equal(t, testRoleName, selectRoleResp.Results[0].Role.GetName()) + assert.Equal(t, 0, len(selectRoleResp.Results[0].Users)) + + // Drop a role with force drop. + status, err = core.DropRole(context.Background(), &milvuspb.DropRoleRequest{ + RoleName: testRoleName, + ForceDrop: true, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + selectRoleResp, err = core.SelectRole(context.Background(), &milvuspb.SelectRoleRequest{ + Role: &milvuspb.RoleEntity{ + Name: testRoleName, + }, + }) + require.NoError(t, merr.CheckRPCCall(status, err)) + assert.Equal(t, 0, len(selectRoleResp.Results)) +} diff --git a/internal/rootcoord/meta_rbac.go b/internal/rootcoord/meta_rbac.go new file mode 100644 index 0000000000..18342e7c70 --- /dev/null +++ b/internal/rootcoord/meta_rbac.go @@ -0,0 +1,62 @@ +package rootcoord + +import ( + "context" + + "github.com/cockroachdb/errors" + + "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" + "github.com/milvus-io/milvus/pkg/v2/proto/internalpb" +) + +var ( + errEmptyUsername = errors.New("username is empty") + errUserNotFound = errors.New("user not found") + errUserAlreadyExists = errors.New("user already exists") + + errEmptyRoleName = errors.New("role name is empty") + errRoleAlreadyExists = errors.New("role already exists") + errRoleNotExists = errors.New("role not exists") + + errEmptyRBACMeta = errors.New("rbac meta is empty") + errNotCustomPrivilegeGroup = errors.New("not a custom privilege group") + + errEmptyPrivilegeGroupName = errors.New("privilege group name is empty") +) + +type RBACChecker interface { + // CheckIfAddCredential checks if the credential can be added. + // if the credential already exists, it will return errUserAlreadyExists. + CheckIfAddCredential(ctx context.Context, req *internalpb.CredentialInfo) error + + // CheckIfUpdateCredential checks if the credential can be updated. + // if the credential not exists, it will return errUserNotFound. + CheckIfUpdateCredential(ctx context.Context, req *internalpb.CredentialInfo) error + + // CheckIfDeleteCredential checks if the credential can be deleted. + // if the credential not exists, it will return errUserNotFound. + CheckIfDeleteCredential(ctx context.Context, req *milvuspb.DeleteCredentialRequest) error + + // CheckIfCreateRole checks if the role can be created. + // if the role already exists, it will return errRoleAlreadyExists. + CheckIfCreateRole(ctx context.Context, req *milvuspb.CreateRoleRequest) error + + // CheckIfDropRole checks if the role can be dropped. + // if the role not exists, it will return errRoleNotExists. + CheckIfDropRole(ctx context.Context, in *milvuspb.DropRoleRequest) error + + // CheckIfOperateUserRole checks if the user role can be operated. + CheckIfOperateUserRole(ctx context.Context, req *milvuspb.OperateUserRoleRequest) error + + // CheckIfPrivilegeGroupCreatable checks if the privilege group can be created. + CheckIfPrivilegeGroupCreatable(ctx context.Context, req *milvuspb.CreatePrivilegeGroupRequest) error + + // CheckIfPrivilegeGroupAlterable checks if the privilege group can be altered. + CheckIfPrivilegeGroupAlterable(ctx context.Context, req *milvuspb.OperatePrivilegeGroupRequest) error + + // CheckIfPrivilegeGroupDropable checks if the privilege group can be dropped. + CheckIfPrivilegeGroupDropable(ctx context.Context, req *milvuspb.DropPrivilegeGroupRequest) error + + // CheckIfRBACRestorable checks if the rbac meta data can be restored. + CheckIfRBACRestorable(ctx context.Context, req *milvuspb.RestoreRBACMetaRequest) error +} diff --git a/internal/rootcoord/meta_table.go b/internal/rootcoord/meta_table.go index b1243f6d1e..4f54e81354 100644 --- a/internal/rootcoord/meta_table.go +++ b/internal/rootcoord/meta_table.go @@ -39,8 +39,10 @@ import ( pb "github.com/milvus-io/milvus/pkg/v2/proto/etcdpb" "github.com/milvus-io/milvus/pkg/v2/proto/internalpb" "github.com/milvus-io/milvus/pkg/v2/proto/rootcoordpb" + "github.com/milvus-io/milvus/pkg/v2/streaming/util/message" "github.com/milvus-io/milvus/pkg/v2/util" "github.com/milvus-io/milvus/pkg/v2/util/contextutil" + "github.com/milvus-io/milvus/pkg/v2/util/crypto" "github.com/milvus-io/milvus/pkg/v2/util/funcutil" "github.com/milvus-io/milvus/pkg/v2/util/merr" "github.com/milvus-io/milvus/pkg/v2/util/paramtable" @@ -48,8 +50,14 @@ import ( "github.com/milvus-io/milvus/pkg/v2/util/typeutil" ) +type MetaTableChecker interface { + RBACChecker +} + //go:generate mockery --name=IMetaTable --structname=MockIMetaTable --output=./ --filename=mock_meta_table.go --with-expecter --inpackage type IMetaTable interface { + MetaTableChecker + GetDatabaseByID(ctx context.Context, dbID int64, ts Timestamp) (*model.Database, error) GetDatabaseByName(ctx context.Context, dbName string, ts Timestamp) (*model.Database, error) CreateDatabase(ctx context.Context, db *model.Database, ts typeutil.Timestamp) error @@ -91,10 +99,10 @@ type IMetaTable interface { IsAlias(ctx context.Context, db, name string) bool ListAliasesByID(ctx context.Context, collID UniqueID) []string - AddCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error GetCredential(ctx context.Context, username string) (*internalpb.CredentialInfo, error) - DeleteCredential(ctx context.Context, username string) error - AlterCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error + InitCredential(ctx context.Context) error + DeleteCredential(ctx context.Context, result message.BroadcastResultDropUserMessageV2) error + AlterCredential(ctx context.Context, result message.BroadcastResultAlterUserMessageV2) error ListCredentialUsernames(ctx context.Context) (*milvuspb.ListCredUsersResponse, error) CreateRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error @@ -1353,47 +1361,103 @@ func (mt *MetaTable) GetGeneralCount(ctx context.Context) int { return mt.generalCnt } -// AddCredential add credential -func (mt *MetaTable) AddCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error { - if credInfo.Username == "" { - return errors.New("username is empty") - } +func (mt *MetaTable) InitCredential(ctx context.Context) error { mt.permissionLock.Lock() defer mt.permissionLock.Unlock() + credInfo, err := mt.catalog.GetCredential(ctx, util.UserRoot) + if err != nil && !errors.Is(err, merr.ErrIoKeyNotFound) { + return err + } + if credInfo != nil { + return nil + } + encryptedRootPassword, err := crypto.PasswordEncrypt(Params.CommonCfg.DefaultRootPassword.GetValue()) + if err != nil { + log.Ctx(ctx).Warn("RootCoord init user root failed", zap.Error(err)) + return err + } + log.Ctx(ctx).Info("RootCoord init user root") + err = mt.catalog.AlterCredential(ctx, &model.Credential{ + Username: util.UserRoot, + EncryptedPassword: encryptedRootPassword, + }) + if err != nil { + log.Ctx(ctx).Warn("RootCoord init user root failed", zap.Error(err)) + return err + } + return nil +} + +func (mt *MetaTable) CheckIfAddCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error { + if funcutil.IsEmptyString(credInfo.GetUsername()) { + return errEmptyUsername + } + mt.permissionLock.RLock() + defer mt.permissionLock.RUnlock() + usernames, err := mt.catalog.ListCredentials(ctx) if err != nil { return err } - if len(usernames) >= Params.ProxyCfg.MaxUserNum.GetAsInt() { + // check if the username already exists. + for _, username := range usernames { + if username == credInfo.GetUsername() { + return errUserAlreadyExists + } + } + + // check if the number of users has reached the limit. + maxUserNum := Params.ProxyCfg.MaxUserNum.GetAsInt() + if len(usernames) >= maxUserNum { errMsg := "unable to add user because the number of users has reached the limit" - log.Ctx(ctx).Error(errMsg, zap.Int("max_user_num", Params.ProxyCfg.MaxUserNum.GetAsInt())) + log.Ctx(ctx).Error(errMsg, zap.Int("maxUserNum", maxUserNum)) return errors.New(errMsg) } + return nil +} - if origin, _ := mt.catalog.GetCredential(ctx, credInfo.Username); origin != nil { - return fmt.Errorf("user already exists: %s", credInfo.Username) +func (mt *MetaTable) CheckIfUpdateCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error { + if funcutil.IsEmptyString(credInfo.GetUsername()) { + return errEmptyUsername } + mt.permissionLock.RLock() + defer mt.permissionLock.RUnlock() - credential := &model.Credential{ - Username: credInfo.Username, - EncryptedPassword: credInfo.EncryptedPassword, + // check if the number of credential exists. + if _, err := mt.catalog.GetCredential(ctx, credInfo.GetUsername()); err != nil { + if errors.Is(err, merr.ErrIoKeyNotFound) { + return errUserNotFound + } + return err } - return mt.catalog.CreateCredential(ctx, credential) + return nil } // AlterCredential update credential -func (mt *MetaTable) AlterCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error { - if credInfo.Username == "" { - return errors.New("username is empty") - } +func (mt *MetaTable) AlterCredential(ctx context.Context, result message.BroadcastResultAlterUserMessageV2) error { + body := result.Message.MustBody() mt.permissionLock.Lock() defer mt.permissionLock.Unlock() + existsCredential, err := mt.catalog.GetCredential(ctx, body.CredentialInfo.Username) + if err != nil && !errors.Is(err, merr.ErrIoKeyNotFound) { + return err + } + // if the credential already exists and the version is not greater than the current timetick. + if existsCredential != nil && existsCredential.TimeTick >= result.GetControlChannelResult().TimeTick { + log.Info("credential already exists and the version is not greater than the current timetick", + zap.String("username", body.CredentialInfo.Username), + zap.Uint64("incoming", result.GetControlChannelResult().TimeTick), + zap.Uint64("current", existsCredential.TimeTick), + ) + return nil + } credential := &model.Credential{ - Username: credInfo.Username, - EncryptedPassword: credInfo.EncryptedPassword, + Username: body.CredentialInfo.Username, + EncryptedPassword: body.CredentialInfo.EncryptedPassword, + TimeTick: result.GetControlChannelResult().TimeTick, } return mt.catalog.AlterCredential(ctx, credential) } @@ -1407,12 +1471,42 @@ func (mt *MetaTable) GetCredential(ctx context.Context, username string) (*inter return model.MarshalCredentialModel(credential), err } +func (mt *MetaTable) CheckIfDeleteCredential(ctx context.Context, req *milvuspb.DeleteCredentialRequest) error { + if funcutil.IsEmptyString(req.GetUsername()) { + return errEmptyUsername + } + mt.permissionLock.RLock() + defer mt.permissionLock.RUnlock() + + // check if the number of credential exists. + if _, err := mt.catalog.GetCredential(ctx, req.GetUsername()); err != nil { + if errors.Is(err, merr.ErrIoKeyNotFound) { + return errUserNotFound + } + return err + } + return nil +} + // DeleteCredential delete credential -func (mt *MetaTable) DeleteCredential(ctx context.Context, username string) error { +func (mt *MetaTable) DeleteCredential(ctx context.Context, result message.BroadcastResultDropUserMessageV2) error { mt.permissionLock.Lock() defer mt.permissionLock.Unlock() - return mt.catalog.DropCredential(ctx, username) + existsCredential, err := mt.catalog.GetCredential(ctx, result.Message.Header().UserName) + if err != nil && !errors.Is(err, merr.ErrIoKeyNotFound) { + return err + } + // if the credential already exists and the version is not greater than the current timetick. + if existsCredential != nil && existsCredential.TimeTick >= result.GetControlChannelResult().TimeTick { + log.Info("credential already exists and the version is not greater than the current timetick", + zap.String("username", result.Message.Header().UserName), + zap.Uint64("incoming", result.GetControlChannelResult().TimeTick), + zap.Uint64("current", existsCredential.TimeTick), + ) + return nil + } + return mt.catalog.DropCredential(ctx, result.Message.Header().UserName) } // ListCredentialUsernames list credential usernames @@ -1427,23 +1521,23 @@ func (mt *MetaTable) ListCredentialUsernames(ctx context.Context) (*milvuspb.Lis return &milvuspb.ListCredUsersResponse{Usernames: usernames}, nil } -// CreateRole create role -func (mt *MetaTable) CreateRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error { - if funcutil.IsEmptyString(entity.Name) { - return errors.New("the role name in the role info is empty") +// CheckIfCreateRole checks if the role can be created. +func (mt *MetaTable) CheckIfCreateRole(ctx context.Context, in *milvuspb.CreateRoleRequest) error { + if funcutil.IsEmptyString(in.GetEntity().GetName()) { + return errEmptyRoleName } - mt.permissionLock.Lock() - defer mt.permissionLock.Unlock() + mt.permissionLock.RLock() + defer mt.permissionLock.RUnlock() - results, err := mt.catalog.ListRole(ctx, tenant, nil, false) + results, err := mt.catalog.ListRole(ctx, util.DefaultTenant, nil, false) if err != nil { log.Ctx(ctx).Warn("fail to list roles", zap.Error(err)) return err } for _, result := range results { - if result.GetRole().GetName() == entity.Name { - log.Ctx(ctx).Info("role already exists", zap.String("role", entity.Name)) - return common.NewIgnorableError(errors.Newf("role [%s] already exists", entity)) + if result.GetRole().GetName() == in.GetEntity().GetName() { + log.Ctx(ctx).Info("role already exists", zap.String("role", in.GetEntity().GetName())) + return errRoleAlreadyExists } } if len(results) >= Params.ProxyCfg.MaxRoleNum.GetAsInt() { @@ -1451,10 +1545,51 @@ func (mt *MetaTable) CreateRole(ctx context.Context, tenant string, entity *milv log.Ctx(ctx).Warn(errMsg, zap.Int("max_role_num", Params.ProxyCfg.MaxRoleNum.GetAsInt())) return errors.New(errMsg) } + return nil +} + +// CreateRole create role +func (mt *MetaTable) CreateRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error { + mt.permissionLock.Lock() + defer mt.permissionLock.Unlock() return mt.catalog.CreateRole(ctx, tenant, entity) } +func (mt *MetaTable) CheckIfDropRole(ctx context.Context, in *milvuspb.DropRoleRequest) error { + if funcutil.IsEmptyString(in.GetRoleName()) { + return errEmptyRoleName + } + if util.IsBuiltinRole(in.GetRoleName()) { + return merr.WrapErrPrivilegeNotPermitted("the role[%s] is a builtin role, which can't be dropped", in.GetRoleName()) + } + mt.permissionLock.RLock() + defer mt.permissionLock.RUnlock() + + if _, err := mt.catalog.ListRole(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: in.GetRoleName()}, false); err != nil { + if errors.Is(err, merr.ErrIoKeyNotFound) { + return errRoleNotExists + } + return err + } + if in.GetForceDrop() { + return nil + } + + grantEntities, err := mt.catalog.ListGrant(ctx, util.DefaultTenant, &milvuspb.GrantEntity{ + Role: &milvuspb.RoleEntity{Name: in.GetRoleName()}, + DbName: "*", + }) + if err != nil { + return err + } + if len(grantEntities) != 0 { + errMsg := "fail to drop the role that it has privileges. Use REVOKE API to revoke privileges" + return errors.New(errMsg) + } + return nil +} + // DropRole drop role info func (mt *MetaTable) DropRole(ctx context.Context, tenant string, roleName string) error { mt.permissionLock.Lock() @@ -1463,15 +1598,33 @@ func (mt *MetaTable) DropRole(ctx context.Context, tenant string, roleName strin return mt.catalog.DropRole(ctx, tenant, roleName) } -// OperateUserRole operate the relationship between a user and a role, including adding a user to a role and removing a user from a role -func (mt *MetaTable) OperateUserRole(ctx context.Context, tenant string, userEntity *milvuspb.UserEntity, roleEntity *milvuspb.RoleEntity, operateType milvuspb.OperateUserRoleType) error { - if funcutil.IsEmptyString(userEntity.Name) { +func (mt *MetaTable) CheckIfOperateUserRole(ctx context.Context, req *milvuspb.OperateUserRoleRequest) error { + if funcutil.IsEmptyString(req.GetUsername()) { return errors.New("username in the user entity is empty") } - if funcutil.IsEmptyString(roleEntity.Name) { + if funcutil.IsEmptyString(req.GetRoleName()) { return errors.New("role name in the role entity is empty") } + mt.permissionLock.RLock() + defer mt.permissionLock.RUnlock() + if _, err := mt.catalog.ListRole(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: req.RoleName}, false); err != nil { + if errors.Is(err, merr.ErrIoKeyNotFound) { + return errRoleNotExists + } + return err + } + if req.Type != milvuspb.OperateUserRoleType_RemoveUserFromRole { + if _, err := mt.catalog.ListUser(ctx, util.DefaultTenant, &milvuspb.UserEntity{Name: req.Username}, false); err != nil { + errMsg := "not found the user, maybe the user isn't existed or internal system error" + return errors.New(errMsg) + } + } + return nil +} + +// OperateUserRole operate the relationship between a user and a role, including adding a user to a role and removing a user from a role +func (mt *MetaTable) OperateUserRole(ctx context.Context, tenant string, userEntity *milvuspb.UserEntity, roleEntity *milvuspb.RoleEntity, operateType milvuspb.OperateUserRoleType) error { mt.permissionLock.Lock() defer mt.permissionLock.Unlock() @@ -1584,6 +1737,72 @@ func (mt *MetaTable) BackupRBAC(ctx context.Context, tenant string) (*milvuspb.R return mt.catalog.BackupRBAC(ctx, tenant) } +func (mt *MetaTable) CheckIfRBACRestorable(ctx context.Context, req *milvuspb.RestoreRBACMetaRequest) error { + meta := req.GetRBACMeta() + if len(meta.GetRoles()) == 0 && len(meta.GetPrivilegeGroups()) == 0 && len(meta.GetGrants()) == 0 && len(meta.GetUsers()) == 0 { + return errEmptyRBACMeta + } + + mt.permissionLock.RLock() + defer mt.permissionLock.RUnlock() + + // check if role already exists + existRoles, err := mt.catalog.ListRole(ctx, util.DefaultTenant, nil, false) + if err != nil { + return err + } + existRoleMap := lo.SliceToMap(existRoles, func(entity *milvuspb.RoleResult) (string, struct{}) { return entity.GetRole().GetName(), struct{}{} }) + existRoleAfterRestoreMap := lo.SliceToMap(existRoles, func(entity *milvuspb.RoleResult) (string, struct{}) { return entity.GetRole().GetName(), struct{}{} }) + for _, role := range meta.GetRoles() { + if _, ok := existRoleMap[role.GetName()]; ok { + return errors.Newf("role [%s] already exists", role.GetName()) + } + existRoleAfterRestoreMap[role.GetName()] = struct{}{} + } + + // check if privilege group already exists + existPrivGroups, err := mt.catalog.ListPrivilegeGroups(ctx) + if err != nil { + return err + } + existPrivGroupMap := lo.SliceToMap(existPrivGroups, func(entity *milvuspb.PrivilegeGroupInfo) (string, struct{}) { return entity.GetGroupName(), struct{}{} }) + existPrivGroupAfterRestoreMap := lo.SliceToMap(existPrivGroups, func(entity *milvuspb.PrivilegeGroupInfo) (string, struct{}) { return entity.GetGroupName(), struct{}{} }) + for _, group := range meta.GetPrivilegeGroups() { + if _, ok := existPrivGroupMap[group.GetGroupName()]; ok { + return errors.Newf("privilege group [%s] already exists", group.GetGroupName()) + } + existPrivGroupAfterRestoreMap[group.GetGroupName()] = struct{}{} + } + + // check if grant can be restored + for _, grant := range meta.GetGrants() { + privName := grant.GetGrantor().GetPrivilege().GetName() + if _, ok := existPrivGroupAfterRestoreMap[privName]; !ok && !util.IsPrivilegeNameDefined(privName) { + return errors.Newf("privilege [%s] does not exist", privName) + } + } + + // check if user can be restored + existUser, err := mt.catalog.ListUser(ctx, util.DefaultTenant, nil, false) + if err != nil { + return err + } + existUserMap := lo.SliceToMap(existUser, func(entity *milvuspb.UserResult) (string, struct{}) { return entity.GetUser().GetName(), struct{}{} }) + for _, user := range meta.GetUsers() { + if _, ok := existUserMap[user.GetUser()]; ok { + return errors.Newf("user [%s] already exists", user.GetUser()) + } + + // check if user-role can be restored + for _, role := range user.GetRoles() { + if _, ok := existRoleAfterRestoreMap[role.GetName()]; !ok { + return errors.Newf("role [%s] does not exist", role.GetName()) + } + } + } + return nil +} + func (mt *MetaTable) RestoreRBAC(ctx context.Context, tenant string, meta *milvuspb.RBACMeta) error { mt.permissionLock.Lock() defer mt.permissionLock.Unlock() @@ -1605,23 +1824,30 @@ func (mt *MetaTable) IsCustomPrivilegeGroup(ctx context.Context, groupName strin return false, nil } -func (mt *MetaTable) CreatePrivilegeGroup(ctx context.Context, groupName string) error { - if funcutil.IsEmptyString(groupName) { - return errors.New("the privilege group name is empty") +func (mt *MetaTable) CheckIfPrivilegeGroupCreatable(ctx context.Context, req *milvuspb.CreatePrivilegeGroupRequest) error { + if funcutil.IsEmptyString(req.GetGroupName()) { + return errEmptyPrivilegeGroupName } - mt.permissionLock.Lock() - defer mt.permissionLock.Unlock() + mt.permissionLock.RLock() + defer mt.permissionLock.RUnlock() - definedByUsers, err := mt.IsCustomPrivilegeGroup(ctx, groupName) + definedByUsers, err := mt.IsCustomPrivilegeGroup(ctx, req.GetGroupName()) if err != nil { return err } if definedByUsers { - return merr.WrapErrParameterInvalidMsg("privilege group name [%s] is defined by users", groupName) + return merr.WrapErrParameterInvalidMsg("privilege group name [%s] is defined by users", req.GetGroupName()) } - if util.IsPrivilegeNameDefined(groupName) { - return merr.WrapErrParameterInvalidMsg("privilege group name [%s] is defined by built in privileges or privilege groups in system", groupName) + if util.IsPrivilegeNameDefined(req.GetGroupName()) { + return merr.WrapErrParameterInvalidMsg("privilege group name [%s] is defined by built in privileges or privilege groups in system", req.GetGroupName()) } + return nil +} + +func (mt *MetaTable) CreatePrivilegeGroup(ctx context.Context, groupName string) error { + mt.permissionLock.Lock() + defer mt.permissionLock.Unlock() + data := &milvuspb.PrivilegeGroupInfo{ GroupName: groupName, Privileges: make([]*milvuspb.PrivilegeEntity, 0), @@ -1629,20 +1855,21 @@ func (mt *MetaTable) CreatePrivilegeGroup(ctx context.Context, groupName string) return mt.catalog.SavePrivilegeGroup(ctx, data) } -func (mt *MetaTable) DropPrivilegeGroup(ctx context.Context, groupName string) error { - if funcutil.IsEmptyString(groupName) { - return errors.New("the privilege group name is empty") +func (mt *MetaTable) CheckIfPrivilegeGroupDropable(ctx context.Context, req *milvuspb.DropPrivilegeGroupRequest) error { + if funcutil.IsEmptyString(req.GetGroupName()) { + return errEmptyPrivilegeGroupName } - mt.permissionLock.Lock() - defer mt.permissionLock.Unlock() + mt.permissionLock.RLock() + defer mt.permissionLock.RUnlock() - definedByUsers, err := mt.IsCustomPrivilegeGroup(ctx, groupName) + definedByUsers, err := mt.IsCustomPrivilegeGroup(ctx, req.GetGroupName()) if err != nil { return err } if !definedByUsers { - return nil + return errNotCustomPrivilegeGroup } + // check if the group is used by any role roles, err := mt.catalog.ListRole(ctx, util.DefaultTenant, nil, false) if err != nil { @@ -1660,11 +1887,18 @@ func (mt *MetaTable) DropPrivilegeGroup(ctx context.Context, groupName string) e return err } for _, grant := range grants { - if grant.Grantor.Privilege.Name == groupName { - return errors.Newf("privilege group [%s] is used by role [%s], Use REVOKE API to revoke it first", groupName, role.GetName()) + if grant.Grantor.Privilege.Name == req.GetGroupName() { + return errors.Newf("privilege group [%s] is used by role [%s], Use REVOKE API to revoke it first", req.GetGroupName(), role.GetName()) } } } + return nil +} + +func (mt *MetaTable) DropPrivilegeGroup(ctx context.Context, groupName string) error { + mt.permissionLock.Lock() + defer mt.permissionLock.Unlock() + return mt.catalog.DropPrivilegeGroup(ctx, groupName) } @@ -1675,43 +1909,55 @@ func (mt *MetaTable) ListPrivilegeGroups(ctx context.Context) ([]*milvuspb.Privi return mt.catalog.ListPrivilegeGroups(ctx) } -func (mt *MetaTable) OperatePrivilegeGroup(ctx context.Context, groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error { - if funcutil.IsEmptyString(groupName) { - return errors.New("the privilege group name is empty") +// CheckIfPrivilegeGroupAlterable checks if the privilege group can be altered. +func (mt *MetaTable) CheckIfPrivilegeGroupAlterable(ctx context.Context, req *milvuspb.OperatePrivilegeGroupRequest) error { + if funcutil.IsEmptyString(req.GetGroupName()) { + return errEmptyPrivilegeGroupName } - mt.permissionLock.Lock() - defer mt.permissionLock.Unlock() + mt.permissionLock.RLock() + defer mt.permissionLock.RUnlock() - if util.IsBuiltinPrivilegeGroup(groupName) { - return merr.WrapErrParameterInvalidMsg("the privilege group name [%s] is defined by built in privilege groups in system", groupName) - } - - // validate input params - definedByUsers, err := mt.IsCustomPrivilegeGroup(ctx, groupName) - if err != nil { - return err - } - if !definedByUsers { - return merr.WrapErrParameterInvalidMsg("there is no privilege group name [%s] to operate", groupName) - } groups, err := mt.catalog.ListPrivilegeGroups(ctx) if err != nil { return err } - for _, p := range privileges { + currenctGroups := lo.SliceToMap(groups, func(group *milvuspb.PrivilegeGroupInfo) (string, []*milvuspb.PrivilegeEntity) { + return group.GroupName, group.Privileges + }) + // check if the privilege group is defined by users + if _, ok := currenctGroups[req.GroupName]; !ok { + return merr.WrapErrParameterInvalidMsg("there is no privilege group name [%s] defined in system to operate", req.GroupName) + } + + if len(req.Privileges) == 0 { + return merr.WrapErrParameterInvalidMsg("privileges is empty when alter the privilege group") + } + // check if the new incoming privileges are defined by users or built in + for _, p := range req.Privileges { if util.IsPrivilegeNameDefined(p.Name) { continue } - for _, group := range groups { - // add privileges for custom privilege group - if group.GroupName == p.Name { - privileges = append(privileges, group.Privileges...) - } else { - return merr.WrapErrParameterInvalidMsg("there is no privilege name or privilege group name [%s] defined in system to operate", p.Name) - } + if _, ok := currenctGroups[p.Name]; !ok { + return merr.WrapErrParameterInvalidMsg("there is no privilege name or privilege group name [%s] defined in system to operate", p.Name) } } + if req.Type == milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup { + // Check if all privileges are the same privilege level + privilegeLevels := lo.SliceToMap(lo.Union(req.Privileges, currenctGroups[req.GroupName]), func(p *milvuspb.PrivilegeEntity) (string, struct{}) { + return util.GetPrivilegeLevel(p.Name), struct{}{} + }) + if len(privilegeLevels) > 1 { + return merr.WrapErrParameterInvalidMsg("privileges are not the same privilege level") + } + } + return nil +} + +func (mt *MetaTable) OperatePrivilegeGroup(ctx context.Context, groupName string, privileges []*milvuspb.PrivilegeEntity, operateType milvuspb.OperatePrivilegeGroupType) error { + mt.permissionLock.Lock() + defer mt.permissionLock.Unlock() + // merge with current privileges group, err := mt.catalog.GetPrivilegeGroup(ctx, groupName) if err != nil { diff --git a/internal/rootcoord/meta_table_test.go b/internal/rootcoord/meta_table_test.go index 9f29b576d0..0bd824b4c3 100644 --- a/internal/rootcoord/meta_table_test.go +++ b/internal/rootcoord/meta_table_test.go @@ -28,31 +28,80 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v2/commonpb" "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" - memkv "github.com/milvus-io/milvus/internal/kv/mem" + etcdkv "github.com/milvus-io/milvus/internal/kv/etcd" "github.com/milvus-io/milvus/internal/metastore/kv/rootcoord" "github.com/milvus-io/milvus/internal/metastore/mocks" "github.com/milvus-io/milvus/internal/metastore/model" "github.com/milvus-io/milvus/internal/streamingcoord/server/balancer/channel" mocktso "github.com/milvus-io/milvus/internal/tso/mocks" - "github.com/milvus-io/milvus/pkg/v2/common" + kvfactory "github.com/milvus-io/milvus/internal/util/dependency/kv" pb "github.com/milvus-io/milvus/pkg/v2/proto/etcdpb" "github.com/milvus-io/milvus/pkg/v2/proto/internalpb" + "github.com/milvus-io/milvus/pkg/v2/streaming/util/message" "github.com/milvus-io/milvus/pkg/v2/util" + "github.com/milvus-io/milvus/pkg/v2/util/funcutil" "github.com/milvus-io/milvus/pkg/v2/util/merr" "github.com/milvus-io/milvus/pkg/v2/util/paramtable" "github.com/milvus-io/milvus/pkg/v2/util/typeutil" ) -func generateMetaTable(t *testing.T) *MetaTable { - return &MetaTable{catalog: rootcoord.NewCatalog(memkv.NewMemoryKV(), nil)} +func generateMetaTable(_ *testing.T) *MetaTable { + kv, _ := kvfactory.GetEtcdAndPath() + path := funcutil.RandomString(10) + catalogKV := etcdkv.NewEtcdKV(kv, path) + return &MetaTable{catalog: rootcoord.NewCatalog(catalogKV, nil)} } -func TestRbacAddCredential(t *testing.T) { +func buildAlterUserMessage(credInfo *internalpb.CredentialInfo, timetick uint64) message.BroadcastResultAlterUserMessageV2 { + msg := message.NewAlterUserMessageBuilderV2(). + WithHeader(&message.AlterUserMessageHeader{ + UserEntity: &milvuspb.UserEntity{ + Name: credInfo.Username, + }, + }). + WithBody(&message.AlterUserMessageBody{ + CredentialInfo: credInfo, + }). + WithBroadcast([]string{funcutil.GetControlChannel("by-dev-rootcoord-dml_1")}). + MustBuildBroadcast() + return message.BroadcastResultAlterUserMessageV2{ + Message: message.MustAsBroadcastAlterUserMessageV2(msg), + Results: map[string]*message.AppendResult{ + funcutil.GetControlChannel("by-dev-rootcoord-dml_1"): {TimeTick: timetick}, + }, + } +} + +func buildDropUserMessage(credInfo *internalpb.CredentialInfo, timetick uint64) message.BroadcastResultDropUserMessageV2 { + msg := message.NewDropUserMessageBuilderV2(). + WithHeader(&message.DropUserMessageHeader{ + UserName: credInfo.Username, + }). + WithBody(&message.DropUserMessageBody{}). + WithBroadcast([]string{funcutil.GetControlChannel("by-dev-rootcoord-dml_1")}). + MustBuildBroadcast() + return message.BroadcastResultDropUserMessageV2{ + Message: message.MustAsBroadcastDropUserMessageV2(msg), + Results: map[string]*message.AppendResult{ + funcutil.GetControlChannel("by-dev-rootcoord-dml_1"): {TimeTick: timetick}, + }, + } +} + +func TestRbacCredential(t *testing.T) { mt := generateMetaTable(t) - err := mt.AddCredential(context.TODO(), &internalpb.CredentialInfo{ - Username: "user1", + + username := "user" + funcutil.RandomString(10) + credInfo := &internalpb.CredentialInfo{ + Username: username, Tenant: util.DefaultTenant, - }) + } + err := mt.CheckIfAddCredential(context.TODO(), credInfo) + require.NoError(t, err) + err = mt.AlterCredential(context.TODO(), buildAlterUserMessage(credInfo, 1)) + require.NoError(t, err) + // idempotency + err = mt.AlterCredential(context.TODO(), buildAlterUserMessage(credInfo, 1)) require.NoError(t, err) tests := []struct { @@ -63,7 +112,7 @@ func TestRbacAddCredential(t *testing.T) { }{ {"Empty username", false, &internalpb.CredentialInfo{Username: ""}}, {"exceed MaxUserNum", true, &internalpb.CredentialInfo{Username: "user3", Tenant: util.DefaultTenant}}, - {"user exist", false, &internalpb.CredentialInfo{Username: "user1", Tenant: util.DefaultTenant}}, + {"user exist", false, &internalpb.CredentialInfo{Username: username, Tenant: util.DefaultTenant}}, } for _, test := range tests { @@ -74,10 +123,39 @@ func TestRbacAddCredential(t *testing.T) { paramtable.Get().Save(Params.ProxyCfg.MaxUserNum.Key, "3") } defer paramtable.Get().Reset(Params.ProxyCfg.MaxUserNum.Key) - err := mt.AddCredential(context.TODO(), test.info) + err := mt.CheckIfAddCredential(context.TODO(), test.info) assert.Error(t, err) }) } + + // should be ignored if timetick is too low. + err = mt.AlterCredential(context.TODO(), buildAlterUserMessage(credInfo, 0)) + require.NoError(t, err) + newCred, err := mt.GetCredential(context.TODO(), credInfo.Username) + require.NoError(t, err) + assert.Equal(t, newCred.TimeTick, uint64(1)) + + err = mt.AlterCredential(context.TODO(), buildAlterUserMessage(credInfo, 2)) + require.NoError(t, err) + newCred, err = mt.GetCredential(context.TODO(), credInfo.Username) + require.NoError(t, err) + assert.Equal(t, newCred.TimeTick, uint64(2)) + + // should be ignored if timetick is too low. + err = mt.DeleteCredential(context.TODO(), buildDropUserMessage(credInfo, 2)) + require.NoError(t, err) + newCred, err = mt.GetCredential(context.TODO(), credInfo.Username) + require.NoError(t, err) + assert.Equal(t, newCred.TimeTick, uint64(2)) + + err = mt.DeleteCredential(context.TODO(), buildDropUserMessage(credInfo, 3)) + require.NoError(t, err) + newCred, err = mt.GetCredential(context.TODO(), credInfo.Username) + require.ErrorIs(t, err, merr.ErrIoKeyNotFound) + require.Nil(t, newCred) + + err = mt.DeleteCredential(context.TODO(), buildDropUserMessage(credInfo, 0)) + require.NoError(t, err) } func TestRbacCreateRole(t *testing.T) { @@ -85,7 +163,11 @@ func TestRbacCreateRole(t *testing.T) { paramtable.Get().Save(Params.ProxyCfg.MaxRoleNum.Key, "2") defer paramtable.Get().Reset(Params.ProxyCfg.MaxRoleNum.Key) - err := mt.CreateRole(context.TODO(), util.DefaultTenant, &milvuspb.RoleEntity{Name: "role1"}) + err := mt.CheckIfCreateRole(context.TODO(), &milvuspb.CreateRoleRequest{Entity: &milvuspb.RoleEntity{Name: "role1"}}) + require.NoError(t, err) + err = mt.CreateRole(context.TODO(), util.DefaultTenant, &milvuspb.RoleEntity{Name: "role1"}) + require.NoError(t, err) + err = mt.CheckIfCreateRole(context.TODO(), &milvuspb.CreateRoleRequest{Entity: &milvuspb.RoleEntity{Name: "role2"}}) require.NoError(t, err) err = mt.CreateRole(context.TODO(), util.DefaultTenant, &milvuspb.RoleEntity{Name: "role2"}) require.NoError(t, err) @@ -101,14 +183,14 @@ func TestRbacCreateRole(t *testing.T) { for _, test := range tests { t.Run(test.description, func(t *testing.T) { - err := mt.CreateRole(context.TODO(), util.DefaultTenant, test.inEntity) + err := mt.CheckIfCreateRole(context.TODO(), &milvuspb.CreateRoleRequest{Entity: test.inEntity}) assert.Error(t, err) }) } t.Run("role has existed", func(t *testing.T) { - err := mt.CreateRole(context.TODO(), util.DefaultTenant, &milvuspb.RoleEntity{Name: "role1"}) + err := mt.CheckIfCreateRole(context.TODO(), &milvuspb.CreateRoleRequest{Entity: &milvuspb.RoleEntity{Name: "role1"}}) assert.Error(t, err) - assert.True(t, common.IsIgnorableError(err)) + assert.True(t, errors.Is(err, errRoleAlreadyExists)) }) { @@ -120,7 +202,7 @@ func TestRbacCreateRole(t *testing.T) { mock.Anything, ).Return(nil, errors.New("error mock list role")) mockMt := &MetaTable{catalog: mockCata} - err := mockMt.CreateRole(context.TODO(), util.DefaultTenant, &milvuspb.RoleEntity{Name: "role1"}) + err := mockMt.CheckIfCreateRole(context.TODO(), &milvuspb.CreateRoleRequest{Entity: &milvuspb.RoleEntity{Name: "role1"}}) assert.Error(t, err) } } @@ -128,24 +210,21 @@ func TestRbacCreateRole(t *testing.T) { func TestRbacDropRole(t *testing.T) { mt := generateMetaTable(t) - err := mt.CreateRole(context.TODO(), util.DefaultTenant, &milvuspb.RoleEntity{Name: "role1"}) + // drop a exist role + roleExist := "role" + funcutil.RandomString(10) + err := mt.CreateRole(context.TODO(), util.DefaultTenant, &milvuspb.RoleEntity{Name: roleExist}) + require.NoError(t, err) + err = mt.CheckIfDropRole(context.TODO(), &milvuspb.DropRoleRequest{RoleName: roleExist}) + require.NoError(t, err) + err = mt.DropRole(context.TODO(), util.DefaultTenant, roleExist) + require.NoError(t, err) + // idempotency + mt.DropRole(context.TODO(), util.DefaultTenant, roleExist) require.NoError(t, err) - tests := []struct { - roleName string - - description string - }{ - {"role1", "drop role1"}, - {"role_not_exists", "drop not exist role"}, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - err := mt.DropRole(context.TODO(), util.DefaultTenant, test.roleName) - assert.NoError(t, err) - }) - } + // drop a not exist role + err = mt.CheckIfDropRole(context.TODO(), &milvuspb.DropRoleRequest{RoleName: "role_not_exist"}) + require.ErrorIs(t, err, errRoleNotExists) } func TestRbacOperateRole(t *testing.T) { @@ -169,7 +248,11 @@ func TestRbacOperateRole(t *testing.T) { for _, test := range tests { t.Run(test.description, func(t *testing.T) { - err := mt.OperateUserRole(context.TODO(), util.DefaultTenant, &milvuspb.UserEntity{Name: test.user}, &milvuspb.RoleEntity{Name: test.role}, test.oType) + err := mt.CheckIfOperateUserRole(context.TODO(), &milvuspb.OperateUserRoleRequest{ + Username: test.user, + RoleName: test.role, + Type: test.oType, + }) assert.Error(t, err) }) } @@ -191,7 +274,7 @@ func TestRbacSelect(t *testing.T) { } for user, rs := range userRoles { - err := mt.catalog.CreateCredential(context.TODO(), &model.Credential{ + err := mt.catalog.AlterCredential(context.TODO(), &model.Credential{ Username: user, Tenant: util.DefaultTenant, }) @@ -2226,23 +2309,55 @@ func TestMetaTable_PrivilegeGroup(t *testing.T) { aliases: newNameDb(), catalog: catalog, } - err := mt.CreatePrivilegeGroup(context.TODO(), "pg1") + err := mt.CheckIfPrivilegeGroupCreatable(context.TODO(), &milvuspb.CreatePrivilegeGroupRequest{ + GroupName: "pg1", + }) assert.Error(t, err) - err = mt.CreatePrivilegeGroup(context.TODO(), "") + err = mt.CheckIfPrivilegeGroupCreatable(context.TODO(), &milvuspb.CreatePrivilegeGroupRequest{ + GroupName: "", + }) assert.Error(t, err) - err = mt.CreatePrivilegeGroup(context.TODO(), "Insert") + err = mt.CheckIfPrivilegeGroupCreatable(context.TODO(), &milvuspb.CreatePrivilegeGroupRequest{ + GroupName: "Insert", + }) assert.Error(t, err) - err = mt.CreatePrivilegeGroup(context.TODO(), "pg2") + err = mt.CheckIfPrivilegeGroupCreatable(context.TODO(), &milvuspb.CreatePrivilegeGroupRequest{ + GroupName: "pg2", + }) assert.NoError(t, err) - err = mt.DropPrivilegeGroup(context.TODO(), "") + err = mt.CreatePrivilegeGroup(context.TODO(), "pg1") + assert.NoError(t, err) + // idempotency + err = mt.CreatePrivilegeGroup(context.TODO(), "pg1") + assert.NoError(t, err) + + err = mt.CheckIfPrivilegeGroupDropable(context.TODO(), &milvuspb.DropPrivilegeGroupRequest{ + GroupName: "", + }) assert.Error(t, err) + err = mt.CheckIfPrivilegeGroupDropable(context.TODO(), &milvuspb.DropPrivilegeGroupRequest{ + GroupName: "pg1", + }) + assert.NoError(t, err) err = mt.DropPrivilegeGroup(context.TODO(), "pg1") assert.NoError(t, err) - err = mt.OperatePrivilegeGroup(context.TODO(), "", []*milvuspb.PrivilegeEntity{}, milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup) + err = mt.CheckIfPrivilegeGroupAlterable(context.TODO(), &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: "", + Privileges: []*milvuspb.PrivilegeEntity{}, + Type: milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup, + }) assert.Error(t, err) - err = mt.OperatePrivilegeGroup(context.TODO(), "ClusterReadOnly", []*milvuspb.PrivilegeEntity{}, milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup) + err = mt.CheckIfPrivilegeGroupAlterable(context.TODO(), &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: "ClusterReadOnly", + Privileges: []*milvuspb.PrivilegeEntity{}, + Type: milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup, + }) assert.Error(t, err) - err = mt.OperatePrivilegeGroup(context.TODO(), "pg3", []*milvuspb.PrivilegeEntity{}, milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup) + err = mt.CheckIfPrivilegeGroupAlterable(context.TODO(), &milvuspb.OperatePrivilegeGroupRequest{ + GroupName: "pg3", + Privileges: []*milvuspb.PrivilegeEntity{}, + Type: milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup, + }) assert.Error(t, err) _, err = mt.GetPrivilegeGroupRoles(context.TODO(), "") assert.Error(t, err) diff --git a/internal/rootcoord/mock_test.go b/internal/rootcoord/mock_test.go index 5768ffb324..a1ce921a5b 100644 --- a/internal/rootcoord/mock_test.go +++ b/internal/rootcoord/mock_test.go @@ -44,6 +44,7 @@ import ( "github.com/milvus-io/milvus/pkg/v2/proto/internalpb" "github.com/milvus-io/milvus/pkg/v2/proto/proxypb" "github.com/milvus-io/milvus/pkg/v2/proto/querypb" + "github.com/milvus-io/milvus/pkg/v2/streaming/util/message" "github.com/milvus-io/milvus/pkg/v2/util/merr" "github.com/milvus-io/milvus/pkg/v2/util/metricsinfo" "github.com/milvus-io/milvus/pkg/v2/util/paramtable" @@ -56,6 +57,8 @@ const ( TestRootCoordID = 200 ) +var _ IMetaTable = &mockMetaTable{} + // TODO: remove mockMetaTable, use mockery instead type mockMetaTable struct { IMetaTable @@ -83,8 +86,8 @@ type mockMetaTable struct { RenameCollectionFunc func(ctx context.Context, oldName string, newName string, ts Timestamp) error AddCredentialFunc func(ctx context.Context, credInfo *internalpb.CredentialInfo) error GetCredentialFunc func(ctx context.Context, username string) (*internalpb.CredentialInfo, error) - DeleteCredentialFunc func(ctx context.Context, username string) error - AlterCredentialFunc func(ctx context.Context, credInfo *internalpb.CredentialInfo) error + DeleteCredentialFunc func(ctx context.Context, msg message.BroadcastResultDropUserMessageV2) error + AlterCredentialFunc func(ctx context.Context, msg message.BroadcastResultAlterUserMessageV2) error ListCredentialUsernamesFunc func(ctx context.Context) (*milvuspb.ListCredUsersResponse, error) CreateRoleFunc func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error DropRoleFunc func(ctx context.Context, tenant string, roleName string) error @@ -205,12 +208,12 @@ func (m mockMetaTable) GetCredential(ctx context.Context, username string) (*int return m.GetCredentialFunc(ctx, username) } -func (m mockMetaTable) DeleteCredential(ctx context.Context, username string) error { - return m.DeleteCredentialFunc(ctx, username) +func (m mockMetaTable) DeleteCredential(ctx context.Context, msg message.BroadcastResultDropUserMessageV2) error { + return m.DeleteCredentialFunc(ctx, msg) } -func (m mockMetaTable) AlterCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error { - return m.AlterCredentialFunc(ctx, credInfo) +func (m mockMetaTable) AlterCredential(ctx context.Context, msg message.BroadcastResultAlterUserMessageV2) error { + return m.AlterCredentialFunc(ctx, msg) } func (m mockMetaTable) ListCredentialUsernames(ctx context.Context) (*milvuspb.ListCredUsersResponse, error) { @@ -373,12 +376,17 @@ func newMockTsoAllocator() *tso.MockAllocator { type mockProxy struct { types.ProxyClient + UpdateCredentialCacheFunc func(ctx context.Context, request *proxypb.UpdateCredCacheRequest) (*commonpb.Status, error) InvalidateCollectionMetaCacheFunc func(ctx context.Context, request *proxypb.InvalidateCollMetaCacheRequest) (*commonpb.Status, error) InvalidateCredentialCacheFunc func(ctx context.Context, request *proxypb.InvalidateCredCacheRequest) (*commonpb.Status, error) RefreshPolicyInfoCacheFunc func(ctx context.Context, request *proxypb.RefreshPolicyInfoCacheRequest) (*commonpb.Status, error) GetComponentStatesFunc func(ctx context.Context) (*milvuspb.ComponentStates, error) } +func (m mockProxy) UpdateCredentialCache(ctx context.Context, request *proxypb.UpdateCredCacheRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { + return m.UpdateCredentialCacheFunc(ctx, request) +} + func (m mockProxy) InvalidateCollectionMetaCache(ctx context.Context, request *proxypb.InvalidateCollMetaCacheRequest, opts ...grpc.CallOption) (*commonpb.Status, error) { return m.InvalidateCollectionMetaCacheFunc(ctx, request) } @@ -426,9 +434,18 @@ func withValidProxyManager() Opt { return func(c *Core) { c.proxyClientManager = proxyutil.NewProxyClientManager(proxyutil.DefaultProxyCreator) p := newMockProxy() + p.UpdateCredentialCacheFunc = func(ctx context.Context, request *proxypb.UpdateCredCacheRequest) (*commonpb.Status, error) { + return merr.Success(), nil + } + p.InvalidateCredentialCacheFunc = func(ctx context.Context, request *proxypb.InvalidateCredCacheRequest) (*commonpb.Status, error) { + return merr.Success(), nil + } p.InvalidateCollectionMetaCacheFunc = func(ctx context.Context, request *proxypb.InvalidateCollMetaCacheRequest) (*commonpb.Status, error) { return merr.Success(), nil } + p.RefreshPolicyInfoCacheFunc = func(ctx context.Context, request *proxypb.RefreshPolicyInfoCacheRequest) (*commonpb.Status, error) { + return merr.Success(), nil + } p.GetComponentStatesFunc = func(ctx context.Context) (*milvuspb.ComponentStates, error) { return &milvuspb.ComponentStates{ State: &milvuspb.ComponentInfo{StateCode: commonpb.StateCode_Healthy}, @@ -509,10 +526,10 @@ func withInvalidMeta() Opt { meta.GetCredentialFunc = func(ctx context.Context, username string) (*internalpb.CredentialInfo, error) { return nil, errors.New("error mock GetCredential") } - meta.DeleteCredentialFunc = func(ctx context.Context, username string) error { + meta.DeleteCredentialFunc = func(ctx context.Context, msg message.BroadcastResultDropUserMessageV2) error { return errors.New("error mock DeleteCredential") } - meta.AlterCredentialFunc = func(ctx context.Context, credInfo *internalpb.CredentialInfo) error { + meta.AlterCredentialFunc = func(ctx context.Context, msg message.BroadcastResultAlterUserMessageV2) error { return errors.New("error mock AlterCredential") } meta.ListCredentialUsernamesFunc = func(ctx context.Context) (*milvuspb.ListCredUsersResponse, error) { diff --git a/internal/rootcoord/mocks/meta_table.go b/internal/rootcoord/mocks/meta_table.go index 632744eaa6..214a672287 100644 --- a/internal/rootcoord/mocks/meta_table.go +++ b/internal/rootcoord/mocks/meta_table.go @@ -8,6 +8,10 @@ import ( etcdpb "github.com/milvus-io/milvus/pkg/v2/proto/etcdpb" internalpb "github.com/milvus-io/milvus/pkg/v2/proto/internalpb" + message "github.com/milvus-io/milvus/pkg/v2/streaming/util/message" + + messagespb "github.com/milvus-io/milvus/pkg/v2/proto/messagespb" + milvuspb "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" mock "github.com/stretchr/testify/mock" @@ -77,53 +81,6 @@ func (_c *IMetaTable_AddCollection_Call) RunAndReturn(run func(context.Context, return _c } -// AddCredential provides a mock function with given fields: ctx, credInfo -func (_m *IMetaTable) AddCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error { - ret := _m.Called(ctx, credInfo) - - if len(ret) == 0 { - panic("no return value specified for AddCredential") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *internalpb.CredentialInfo) error); ok { - r0 = rf(ctx, credInfo) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// IMetaTable_AddCredential_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddCredential' -type IMetaTable_AddCredential_Call struct { - *mock.Call -} - -// AddCredential is a helper method to define mock.On call -// - ctx context.Context -// - credInfo *internalpb.CredentialInfo -func (_e *IMetaTable_Expecter) AddCredential(ctx interface{}, credInfo interface{}) *IMetaTable_AddCredential_Call { - return &IMetaTable_AddCredential_Call{Call: _e.mock.On("AddCredential", ctx, credInfo)} -} - -func (_c *IMetaTable_AddCredential_Call) Run(run func(ctx context.Context, credInfo *internalpb.CredentialInfo)) *IMetaTable_AddCredential_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*internalpb.CredentialInfo)) - }) - return _c -} - -func (_c *IMetaTable_AddCredential_Call) Return(_a0 error) *IMetaTable_AddCredential_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *IMetaTable_AddCredential_Call) RunAndReturn(run func(context.Context, *internalpb.CredentialInfo) error) *IMetaTable_AddCredential_Call { - _c.Call.Return(run) - return _c -} - // AddPartition provides a mock function with given fields: ctx, partition func (_m *IMetaTable) AddPartition(ctx context.Context, partition *model.Partition) error { ret := _m.Called(ctx, partition) @@ -271,17 +228,17 @@ func (_c *IMetaTable_AlterCollection_Call) RunAndReturn(run func(context.Context return _c } -// AlterCredential provides a mock function with given fields: ctx, credInfo -func (_m *IMetaTable) AlterCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error { - ret := _m.Called(ctx, credInfo) +// AlterCredential provides a mock function with given fields: ctx, result +func (_m *IMetaTable) AlterCredential(ctx context.Context, result message.BroadcastResult[*messagespb.AlterUserMessageHeader, *messagespb.AlterUserMessageBody]) error { + ret := _m.Called(ctx, result) if len(ret) == 0 { panic("no return value specified for AlterCredential") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *internalpb.CredentialInfo) error); ok { - r0 = rf(ctx, credInfo) + if rf, ok := ret.Get(0).(func(context.Context, message.BroadcastResult[*messagespb.AlterUserMessageHeader, *messagespb.AlterUserMessageBody]) error); ok { + r0 = rf(ctx, result) } else { r0 = ret.Error(0) } @@ -296,14 +253,14 @@ type IMetaTable_AlterCredential_Call struct { // AlterCredential is a helper method to define mock.On call // - ctx context.Context -// - credInfo *internalpb.CredentialInfo -func (_e *IMetaTable_Expecter) AlterCredential(ctx interface{}, credInfo interface{}) *IMetaTable_AlterCredential_Call { - return &IMetaTable_AlterCredential_Call{Call: _e.mock.On("AlterCredential", ctx, credInfo)} +// - result message.BroadcastResult[*messagespb.AlterUserMessageHeader,*messagespb.AlterUserMessageBody] +func (_e *IMetaTable_Expecter) AlterCredential(ctx interface{}, result interface{}) *IMetaTable_AlterCredential_Call { + return &IMetaTable_AlterCredential_Call{Call: _e.mock.On("AlterCredential", ctx, result)} } -func (_c *IMetaTable_AlterCredential_Call) Run(run func(ctx context.Context, credInfo *internalpb.CredentialInfo)) *IMetaTable_AlterCredential_Call { +func (_c *IMetaTable_AlterCredential_Call) Run(run func(ctx context.Context, result message.BroadcastResult[*messagespb.AlterUserMessageHeader, *messagespb.AlterUserMessageBody])) *IMetaTable_AlterCredential_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*internalpb.CredentialInfo)) + run(args[0].(context.Context), args[1].(message.BroadcastResult[*messagespb.AlterUserMessageHeader, *messagespb.AlterUserMessageBody])) }) return _c } @@ -313,7 +270,7 @@ func (_c *IMetaTable_AlterCredential_Call) Return(_a0 error) *IMetaTable_AlterCr return _c } -func (_c *IMetaTable_AlterCredential_Call) RunAndReturn(run func(context.Context, *internalpb.CredentialInfo) error) *IMetaTable_AlterCredential_Call { +func (_c *IMetaTable_AlterCredential_Call) RunAndReturn(run func(context.Context, message.BroadcastResult[*messagespb.AlterUserMessageHeader, *messagespb.AlterUserMessageBody]) error) *IMetaTable_AlterCredential_Call { _c.Call.Return(run) return _c } @@ -525,6 +482,476 @@ func (_c *IMetaTable_ChangePartitionState_Call) RunAndReturn(run func(context.Co return _c } +// CheckIfAddCredential provides a mock function with given fields: ctx, req +func (_m *IMetaTable) CheckIfAddCredential(ctx context.Context, req *internalpb.CredentialInfo) error { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for CheckIfAddCredential") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *internalpb.CredentialInfo) error); ok { + r0 = rf(ctx, req) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_CheckIfAddCredential_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckIfAddCredential' +type IMetaTable_CheckIfAddCredential_Call struct { + *mock.Call +} + +// CheckIfAddCredential is a helper method to define mock.On call +// - ctx context.Context +// - req *internalpb.CredentialInfo +func (_e *IMetaTable_Expecter) CheckIfAddCredential(ctx interface{}, req interface{}) *IMetaTable_CheckIfAddCredential_Call { + return &IMetaTable_CheckIfAddCredential_Call{Call: _e.mock.On("CheckIfAddCredential", ctx, req)} +} + +func (_c *IMetaTable_CheckIfAddCredential_Call) Run(run func(ctx context.Context, req *internalpb.CredentialInfo)) *IMetaTable_CheckIfAddCredential_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*internalpb.CredentialInfo)) + }) + return _c +} + +func (_c *IMetaTable_CheckIfAddCredential_Call) Return(_a0 error) *IMetaTable_CheckIfAddCredential_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_CheckIfAddCredential_Call) RunAndReturn(run func(context.Context, *internalpb.CredentialInfo) error) *IMetaTable_CheckIfAddCredential_Call { + _c.Call.Return(run) + return _c +} + +// CheckIfCreateRole provides a mock function with given fields: ctx, req +func (_m *IMetaTable) CheckIfCreateRole(ctx context.Context, req *milvuspb.CreateRoleRequest) error { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for CheckIfCreateRole") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreateRoleRequest) error); ok { + r0 = rf(ctx, req) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_CheckIfCreateRole_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckIfCreateRole' +type IMetaTable_CheckIfCreateRole_Call struct { + *mock.Call +} + +// CheckIfCreateRole is a helper method to define mock.On call +// - ctx context.Context +// - req *milvuspb.CreateRoleRequest +func (_e *IMetaTable_Expecter) CheckIfCreateRole(ctx interface{}, req interface{}) *IMetaTable_CheckIfCreateRole_Call { + return &IMetaTable_CheckIfCreateRole_Call{Call: _e.mock.On("CheckIfCreateRole", ctx, req)} +} + +func (_c *IMetaTable_CheckIfCreateRole_Call) Run(run func(ctx context.Context, req *milvuspb.CreateRoleRequest)) *IMetaTable_CheckIfCreateRole_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.CreateRoleRequest)) + }) + return _c +} + +func (_c *IMetaTable_CheckIfCreateRole_Call) Return(_a0 error) *IMetaTable_CheckIfCreateRole_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_CheckIfCreateRole_Call) RunAndReturn(run func(context.Context, *milvuspb.CreateRoleRequest) error) *IMetaTable_CheckIfCreateRole_Call { + _c.Call.Return(run) + return _c +} + +// CheckIfDeleteCredential provides a mock function with given fields: ctx, req +func (_m *IMetaTable) CheckIfDeleteCredential(ctx context.Context, req *milvuspb.DeleteCredentialRequest) error { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for CheckIfDeleteCredential") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.DeleteCredentialRequest) error); ok { + r0 = rf(ctx, req) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_CheckIfDeleteCredential_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckIfDeleteCredential' +type IMetaTable_CheckIfDeleteCredential_Call struct { + *mock.Call +} + +// CheckIfDeleteCredential is a helper method to define mock.On call +// - ctx context.Context +// - req *milvuspb.DeleteCredentialRequest +func (_e *IMetaTable_Expecter) CheckIfDeleteCredential(ctx interface{}, req interface{}) *IMetaTable_CheckIfDeleteCredential_Call { + return &IMetaTable_CheckIfDeleteCredential_Call{Call: _e.mock.On("CheckIfDeleteCredential", ctx, req)} +} + +func (_c *IMetaTable_CheckIfDeleteCredential_Call) Run(run func(ctx context.Context, req *milvuspb.DeleteCredentialRequest)) *IMetaTable_CheckIfDeleteCredential_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.DeleteCredentialRequest)) + }) + return _c +} + +func (_c *IMetaTable_CheckIfDeleteCredential_Call) Return(_a0 error) *IMetaTable_CheckIfDeleteCredential_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_CheckIfDeleteCredential_Call) RunAndReturn(run func(context.Context, *milvuspb.DeleteCredentialRequest) error) *IMetaTable_CheckIfDeleteCredential_Call { + _c.Call.Return(run) + return _c +} + +// CheckIfDropRole provides a mock function with given fields: ctx, in +func (_m *IMetaTable) CheckIfDropRole(ctx context.Context, in *milvuspb.DropRoleRequest) error { + ret := _m.Called(ctx, in) + + if len(ret) == 0 { + panic("no return value specified for CheckIfDropRole") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.DropRoleRequest) error); ok { + r0 = rf(ctx, in) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_CheckIfDropRole_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckIfDropRole' +type IMetaTable_CheckIfDropRole_Call struct { + *mock.Call +} + +// CheckIfDropRole is a helper method to define mock.On call +// - ctx context.Context +// - in *milvuspb.DropRoleRequest +func (_e *IMetaTable_Expecter) CheckIfDropRole(ctx interface{}, in interface{}) *IMetaTable_CheckIfDropRole_Call { + return &IMetaTable_CheckIfDropRole_Call{Call: _e.mock.On("CheckIfDropRole", ctx, in)} +} + +func (_c *IMetaTable_CheckIfDropRole_Call) Run(run func(ctx context.Context, in *milvuspb.DropRoleRequest)) *IMetaTable_CheckIfDropRole_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.DropRoleRequest)) + }) + return _c +} + +func (_c *IMetaTable_CheckIfDropRole_Call) Return(_a0 error) *IMetaTable_CheckIfDropRole_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_CheckIfDropRole_Call) RunAndReturn(run func(context.Context, *milvuspb.DropRoleRequest) error) *IMetaTable_CheckIfDropRole_Call { + _c.Call.Return(run) + return _c +} + +// CheckIfOperateUserRole provides a mock function with given fields: ctx, req +func (_m *IMetaTable) CheckIfOperateUserRole(ctx context.Context, req *milvuspb.OperateUserRoleRequest) error { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for CheckIfOperateUserRole") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.OperateUserRoleRequest) error); ok { + r0 = rf(ctx, req) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_CheckIfOperateUserRole_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckIfOperateUserRole' +type IMetaTable_CheckIfOperateUserRole_Call struct { + *mock.Call +} + +// CheckIfOperateUserRole is a helper method to define mock.On call +// - ctx context.Context +// - req *milvuspb.OperateUserRoleRequest +func (_e *IMetaTable_Expecter) CheckIfOperateUserRole(ctx interface{}, req interface{}) *IMetaTable_CheckIfOperateUserRole_Call { + return &IMetaTable_CheckIfOperateUserRole_Call{Call: _e.mock.On("CheckIfOperateUserRole", ctx, req)} +} + +func (_c *IMetaTable_CheckIfOperateUserRole_Call) Run(run func(ctx context.Context, req *milvuspb.OperateUserRoleRequest)) *IMetaTable_CheckIfOperateUserRole_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.OperateUserRoleRequest)) + }) + return _c +} + +func (_c *IMetaTable_CheckIfOperateUserRole_Call) Return(_a0 error) *IMetaTable_CheckIfOperateUserRole_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_CheckIfOperateUserRole_Call) RunAndReturn(run func(context.Context, *milvuspb.OperateUserRoleRequest) error) *IMetaTable_CheckIfOperateUserRole_Call { + _c.Call.Return(run) + return _c +} + +// CheckIfPrivilegeGroupAlterable provides a mock function with given fields: ctx, req +func (_m *IMetaTable) CheckIfPrivilegeGroupAlterable(ctx context.Context, req *milvuspb.OperatePrivilegeGroupRequest) error { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for CheckIfPrivilegeGroupAlterable") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.OperatePrivilegeGroupRequest) error); ok { + r0 = rf(ctx, req) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_CheckIfPrivilegeGroupAlterable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckIfPrivilegeGroupAlterable' +type IMetaTable_CheckIfPrivilegeGroupAlterable_Call struct { + *mock.Call +} + +// CheckIfPrivilegeGroupAlterable is a helper method to define mock.On call +// - ctx context.Context +// - req *milvuspb.OperatePrivilegeGroupRequest +func (_e *IMetaTable_Expecter) CheckIfPrivilegeGroupAlterable(ctx interface{}, req interface{}) *IMetaTable_CheckIfPrivilegeGroupAlterable_Call { + return &IMetaTable_CheckIfPrivilegeGroupAlterable_Call{Call: _e.mock.On("CheckIfPrivilegeGroupAlterable", ctx, req)} +} + +func (_c *IMetaTable_CheckIfPrivilegeGroupAlterable_Call) Run(run func(ctx context.Context, req *milvuspb.OperatePrivilegeGroupRequest)) *IMetaTable_CheckIfPrivilegeGroupAlterable_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.OperatePrivilegeGroupRequest)) + }) + return _c +} + +func (_c *IMetaTable_CheckIfPrivilegeGroupAlterable_Call) Return(_a0 error) *IMetaTable_CheckIfPrivilegeGroupAlterable_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_CheckIfPrivilegeGroupAlterable_Call) RunAndReturn(run func(context.Context, *milvuspb.OperatePrivilegeGroupRequest) error) *IMetaTable_CheckIfPrivilegeGroupAlterable_Call { + _c.Call.Return(run) + return _c +} + +// CheckIfPrivilegeGroupCreatable provides a mock function with given fields: ctx, req +func (_m *IMetaTable) CheckIfPrivilegeGroupCreatable(ctx context.Context, req *milvuspb.CreatePrivilegeGroupRequest) error { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for CheckIfPrivilegeGroupCreatable") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.CreatePrivilegeGroupRequest) error); ok { + r0 = rf(ctx, req) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_CheckIfPrivilegeGroupCreatable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckIfPrivilegeGroupCreatable' +type IMetaTable_CheckIfPrivilegeGroupCreatable_Call struct { + *mock.Call +} + +// CheckIfPrivilegeGroupCreatable is a helper method to define mock.On call +// - ctx context.Context +// - req *milvuspb.CreatePrivilegeGroupRequest +func (_e *IMetaTable_Expecter) CheckIfPrivilegeGroupCreatable(ctx interface{}, req interface{}) *IMetaTable_CheckIfPrivilegeGroupCreatable_Call { + return &IMetaTable_CheckIfPrivilegeGroupCreatable_Call{Call: _e.mock.On("CheckIfPrivilegeGroupCreatable", ctx, req)} +} + +func (_c *IMetaTable_CheckIfPrivilegeGroupCreatable_Call) Run(run func(ctx context.Context, req *milvuspb.CreatePrivilegeGroupRequest)) *IMetaTable_CheckIfPrivilegeGroupCreatable_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.CreatePrivilegeGroupRequest)) + }) + return _c +} + +func (_c *IMetaTable_CheckIfPrivilegeGroupCreatable_Call) Return(_a0 error) *IMetaTable_CheckIfPrivilegeGroupCreatable_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_CheckIfPrivilegeGroupCreatable_Call) RunAndReturn(run func(context.Context, *milvuspb.CreatePrivilegeGroupRequest) error) *IMetaTable_CheckIfPrivilegeGroupCreatable_Call { + _c.Call.Return(run) + return _c +} + +// CheckIfPrivilegeGroupDropable provides a mock function with given fields: ctx, req +func (_m *IMetaTable) CheckIfPrivilegeGroupDropable(ctx context.Context, req *milvuspb.DropPrivilegeGroupRequest) error { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for CheckIfPrivilegeGroupDropable") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.DropPrivilegeGroupRequest) error); ok { + r0 = rf(ctx, req) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_CheckIfPrivilegeGroupDropable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckIfPrivilegeGroupDropable' +type IMetaTable_CheckIfPrivilegeGroupDropable_Call struct { + *mock.Call +} + +// CheckIfPrivilegeGroupDropable is a helper method to define mock.On call +// - ctx context.Context +// - req *milvuspb.DropPrivilegeGroupRequest +func (_e *IMetaTable_Expecter) CheckIfPrivilegeGroupDropable(ctx interface{}, req interface{}) *IMetaTable_CheckIfPrivilegeGroupDropable_Call { + return &IMetaTable_CheckIfPrivilegeGroupDropable_Call{Call: _e.mock.On("CheckIfPrivilegeGroupDropable", ctx, req)} +} + +func (_c *IMetaTable_CheckIfPrivilegeGroupDropable_Call) Run(run func(ctx context.Context, req *milvuspb.DropPrivilegeGroupRequest)) *IMetaTable_CheckIfPrivilegeGroupDropable_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.DropPrivilegeGroupRequest)) + }) + return _c +} + +func (_c *IMetaTable_CheckIfPrivilegeGroupDropable_Call) Return(_a0 error) *IMetaTable_CheckIfPrivilegeGroupDropable_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_CheckIfPrivilegeGroupDropable_Call) RunAndReturn(run func(context.Context, *milvuspb.DropPrivilegeGroupRequest) error) *IMetaTable_CheckIfPrivilegeGroupDropable_Call { + _c.Call.Return(run) + return _c +} + +// CheckIfRBACRestorable provides a mock function with given fields: ctx, req +func (_m *IMetaTable) CheckIfRBACRestorable(ctx context.Context, req *milvuspb.RestoreRBACMetaRequest) error { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for CheckIfRBACRestorable") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *milvuspb.RestoreRBACMetaRequest) error); ok { + r0 = rf(ctx, req) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_CheckIfRBACRestorable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckIfRBACRestorable' +type IMetaTable_CheckIfRBACRestorable_Call struct { + *mock.Call +} + +// CheckIfRBACRestorable is a helper method to define mock.On call +// - ctx context.Context +// - req *milvuspb.RestoreRBACMetaRequest +func (_e *IMetaTable_Expecter) CheckIfRBACRestorable(ctx interface{}, req interface{}) *IMetaTable_CheckIfRBACRestorable_Call { + return &IMetaTable_CheckIfRBACRestorable_Call{Call: _e.mock.On("CheckIfRBACRestorable", ctx, req)} +} + +func (_c *IMetaTable_CheckIfRBACRestorable_Call) Run(run func(ctx context.Context, req *milvuspb.RestoreRBACMetaRequest)) *IMetaTable_CheckIfRBACRestorable_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*milvuspb.RestoreRBACMetaRequest)) + }) + return _c +} + +func (_c *IMetaTable_CheckIfRBACRestorable_Call) Return(_a0 error) *IMetaTable_CheckIfRBACRestorable_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_CheckIfRBACRestorable_Call) RunAndReturn(run func(context.Context, *milvuspb.RestoreRBACMetaRequest) error) *IMetaTable_CheckIfRBACRestorable_Call { + _c.Call.Return(run) + return _c +} + +// CheckIfUpdateCredential provides a mock function with given fields: ctx, req +func (_m *IMetaTable) CheckIfUpdateCredential(ctx context.Context, req *internalpb.CredentialInfo) error { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for CheckIfUpdateCredential") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *internalpb.CredentialInfo) error); ok { + r0 = rf(ctx, req) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_CheckIfUpdateCredential_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckIfUpdateCredential' +type IMetaTable_CheckIfUpdateCredential_Call struct { + *mock.Call +} + +// CheckIfUpdateCredential is a helper method to define mock.On call +// - ctx context.Context +// - req *internalpb.CredentialInfo +func (_e *IMetaTable_Expecter) CheckIfUpdateCredential(ctx interface{}, req interface{}) *IMetaTable_CheckIfUpdateCredential_Call { + return &IMetaTable_CheckIfUpdateCredential_Call{Call: _e.mock.On("CheckIfUpdateCredential", ctx, req)} +} + +func (_c *IMetaTable_CheckIfUpdateCredential_Call) Run(run func(ctx context.Context, req *internalpb.CredentialInfo)) *IMetaTable_CheckIfUpdateCredential_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*internalpb.CredentialInfo)) + }) + return _c +} + +func (_c *IMetaTable_CheckIfUpdateCredential_Call) Return(_a0 error) *IMetaTable_CheckIfUpdateCredential_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_CheckIfUpdateCredential_Call) RunAndReturn(run func(context.Context, *internalpb.CredentialInfo) error) *IMetaTable_CheckIfUpdateCredential_Call { + _c.Call.Return(run) + return _c +} + // CreateAlias provides a mock function with given fields: ctx, dbName, alias, collectionName, ts func (_m *IMetaTable) CreateAlias(ctx context.Context, dbName string, alias string, collectionName string, ts uint64) error { ret := _m.Called(ctx, dbName, alias, collectionName, ts) @@ -718,17 +1145,17 @@ func (_c *IMetaTable_CreateRole_Call) RunAndReturn(run func(context.Context, str return _c } -// DeleteCredential provides a mock function with given fields: ctx, username -func (_m *IMetaTable) DeleteCredential(ctx context.Context, username string) error { - ret := _m.Called(ctx, username) +// DeleteCredential provides a mock function with given fields: ctx, result +func (_m *IMetaTable) DeleteCredential(ctx context.Context, result message.BroadcastResult[*messagespb.DropUserMessageHeader, *messagespb.DropUserMessageBody]) error { + ret := _m.Called(ctx, result) if len(ret) == 0 { panic("no return value specified for DeleteCredential") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, username) + if rf, ok := ret.Get(0).(func(context.Context, message.BroadcastResult[*messagespb.DropUserMessageHeader, *messagespb.DropUserMessageBody]) error); ok { + r0 = rf(ctx, result) } else { r0 = ret.Error(0) } @@ -743,14 +1170,14 @@ type IMetaTable_DeleteCredential_Call struct { // DeleteCredential is a helper method to define mock.On call // - ctx context.Context -// - username string -func (_e *IMetaTable_Expecter) DeleteCredential(ctx interface{}, username interface{}) *IMetaTable_DeleteCredential_Call { - return &IMetaTable_DeleteCredential_Call{Call: _e.mock.On("DeleteCredential", ctx, username)} +// - result message.BroadcastResult[*messagespb.DropUserMessageHeader,*messagespb.DropUserMessageBody] +func (_e *IMetaTable_Expecter) DeleteCredential(ctx interface{}, result interface{}) *IMetaTable_DeleteCredential_Call { + return &IMetaTable_DeleteCredential_Call{Call: _e.mock.On("DeleteCredential", ctx, result)} } -func (_c *IMetaTable_DeleteCredential_Call) Run(run func(ctx context.Context, username string)) *IMetaTable_DeleteCredential_Call { +func (_c *IMetaTable_DeleteCredential_Call) Run(run func(ctx context.Context, result message.BroadcastResult[*messagespb.DropUserMessageHeader, *messagespb.DropUserMessageBody])) *IMetaTable_DeleteCredential_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(message.BroadcastResult[*messagespb.DropUserMessageHeader, *messagespb.DropUserMessageBody])) }) return _c } @@ -760,7 +1187,7 @@ func (_c *IMetaTable_DeleteCredential_Call) Return(_a0 error) *IMetaTable_Delete return _c } -func (_c *IMetaTable_DeleteCredential_Call) RunAndReturn(run func(context.Context, string) error) *IMetaTable_DeleteCredential_Call { +func (_c *IMetaTable_DeleteCredential_Call) RunAndReturn(run func(context.Context, message.BroadcastResult[*messagespb.DropUserMessageHeader, *messagespb.DropUserMessageBody]) error) *IMetaTable_DeleteCredential_Call { _c.Call.Return(run) return _c } @@ -1676,6 +2103,52 @@ func (_c *IMetaTable_GetPrivilegeGroupRoles_Call) RunAndReturn(run func(context. return _c } +// InitCredential provides a mock function with given fields: ctx +func (_m *IMetaTable) InitCredential(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for InitCredential") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// IMetaTable_InitCredential_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InitCredential' +type IMetaTable_InitCredential_Call struct { + *mock.Call +} + +// InitCredential is a helper method to define mock.On call +// - ctx context.Context +func (_e *IMetaTable_Expecter) InitCredential(ctx interface{}) *IMetaTable_InitCredential_Call { + return &IMetaTable_InitCredential_Call{Call: _e.mock.On("InitCredential", ctx)} +} + +func (_c *IMetaTable_InitCredential_Call) Run(run func(ctx context.Context)) *IMetaTable_InitCredential_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *IMetaTable_InitCredential_Call) Return(_a0 error) *IMetaTable_InitCredential_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *IMetaTable_InitCredential_Call) RunAndReturn(run func(context.Context) error) *IMetaTable_InitCredential_Call { + _c.Call.Return(run) + return _c +} + // IsAlias provides a mock function with given fields: ctx, db, name func (_m *IMetaTable) IsAlias(ctx context.Context, db string, name string) bool { ret := _m.Called(ctx, db, name) diff --git a/internal/rootcoord/rbac_task.go b/internal/rootcoord/rbac_task.go index 70073cde0c..8eb9eaa142 100644 --- a/internal/rootcoord/rbac_task.go +++ b/internal/rootcoord/rbac_task.go @@ -35,171 +35,64 @@ import ( "github.com/milvus-io/milvus/pkg/v2/util/typeutil" ) -func executeDeleteCredentialTaskSteps(ctx context.Context, core *Core, username string) error { - redoTask := newBaseRedoTask(core.stepExecutor) - redoTask.AddSyncStep(NewSimpleStep("delete credential meta data", func(ctx context.Context) ([]nestedStep, error) { - err := core.meta.DeleteCredential(ctx, username) - if err != nil { - log.Ctx(ctx).Warn("delete credential meta data failed", zap.String("username", username), zap.Error(err)) - } - return nil, err - })) - redoTask.AddAsyncStep(NewSimpleStep("delete credential cache", func(ctx context.Context) ([]nestedStep, error) { - err := core.ExpireCredCache(ctx, username) - if err != nil { - log.Ctx(ctx).Warn("delete credential cache failed", zap.String("username", username), zap.Error(err)) - } - return nil, err - })) - redoTask.AddAsyncStep(NewSimpleStep("delete user role cache for the user", func(ctx context.Context) ([]nestedStep, error) { - err := core.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ - OpType: int32(typeutil.CacheDeleteUser), - OpKey: username, - }) - if err != nil { - log.Ctx(ctx).Warn("delete user role cache failed for the user", zap.String("username", username), zap.Error(err)) - } - return nil, err - })) - - return redoTask.Execute(ctx) -} - -func executeDropRoleTaskSteps(ctx context.Context, core *Core, roleName string, foreDrop bool) error { - redoTask := newBaseRedoTask(core.stepExecutor) - redoTask.AddSyncStep(NewSimpleStep("drop role meta data", func(ctx context.Context) ([]nestedStep, error) { - err := core.meta.DropRole(ctx, util.DefaultTenant, roleName) - if err != nil { - log.Ctx(ctx).Warn("drop role mata data failed", zap.String("role_name", roleName), zap.Error(err)) - } - return nil, err - })) - redoTask.AddAsyncStep(NewSimpleStep("drop the privilege list of this role", func(ctx context.Context) ([]nestedStep, error) { - if !foreDrop { - return nil, nil - } - err := core.meta.DropGrant(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: roleName}) - if err != nil { - log.Ctx(ctx).Warn("drop the privilege list failed for the role", zap.String("role_name", roleName), zap.Error(err)) - } - return nil, err - })) - redoTask.AddAsyncStep(NewSimpleStep("drop role cache", func(ctx context.Context) ([]nestedStep, error) { - err := core.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ - OpType: int32(typeutil.CacheDropRole), - OpKey: roleName, - }) - if err != nil { - log.Ctx(ctx).Warn("delete user role cache failed for the role", zap.String("role_name", roleName), zap.Error(err)) - } - return nil, err - })) - return redoTask.Execute(ctx) -} - -func executeOperateUserRoleTaskSteps(ctx context.Context, core *Core, in *milvuspb.OperateUserRoleRequest) error { - username := in.Username - roleName := in.RoleName - operateType := in.Type - redoTask := newBaseRedoTask(core.stepExecutor) - redoTask.AddSyncStep(NewSimpleStep("operate user role meta data", func(ctx context.Context) ([]nestedStep, error) { - err := core.meta.OperateUserRole(ctx, util.DefaultTenant, &milvuspb.UserEntity{Name: username}, &milvuspb.RoleEntity{Name: roleName}, operateType) - if err != nil && !common.IsIgnorableError(err) { - log.Ctx(ctx).Warn("operate user role mata data failed", - zap.String("username", username), zap.String("role_name", roleName), - zap.Any("operate_type", operateType), - zap.Error(err)) - return nil, err - } - return nil, nil - })) - redoTask.AddAsyncStep(NewSimpleStep("operate user role cache", func(ctx context.Context) ([]nestedStep, error) { - var opType int32 - switch operateType { - case milvuspb.OperateUserRoleType_AddUserToRole: - opType = int32(typeutil.CacheAddUserToRole) - case milvuspb.OperateUserRoleType_RemoveUserFromRole: - opType = int32(typeutil.CacheRemoveUserFromRole) - default: - errMsg := "invalid operate type for the OperateUserRole api" - log.Ctx(ctx).Warn(errMsg, - zap.String("username", username), zap.String("role_name", roleName), - zap.Any("operate_type", operateType), - ) - return nil, nil - } - if err := core.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ - OpType: opType, - OpKey: funcutil.EncodeUserRoleCache(username, roleName), - }); err != nil { - log.Ctx(ctx).Warn("fail to refresh policy info cache", - zap.String("username", username), zap.String("role_name", roleName), - zap.Any("operate_type", operateType), - zap.Error(err), - ) - return nil, err - } - return nil, nil - })) - return redoTask.Execute(ctx) -} - -func executeOperatePrivilegeTaskSteps(ctx context.Context, core *Core, in *milvuspb.OperatePrivilegeRequest) error { - privName := in.Entity.Grantor.Privilege.Name - redoTask := newBaseRedoTask(core.stepExecutor) - redoTask.AddSyncStep(NewSimpleStep("operate privilege meta data", func(ctx context.Context) ([]nestedStep, error) { +func executeOperatePrivilegeTaskSteps(ctx context.Context, core *Core, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error { + privName := entity.Grantor.Privilege.Name + if err := func() error { // set up privilege name for metastore dbPrivName, err := core.getMetastorePrivilegeName(ctx, privName) if err != nil { - return nil, err + return err } - in.Entity.Grantor.Privilege.Name = dbPrivName + entity.Grantor.Privilege.Name = dbPrivName - err = core.meta.OperatePrivilege(ctx, util.DefaultTenant, in.Entity, in.Type) + err = core.meta.OperatePrivilege(ctx, util.DefaultTenant, entity, operateType) if err != nil && !common.IsIgnorableError(err) { - log.Ctx(ctx).Warn("fail to operate the privilege", zap.Any("in", in), zap.Error(err)) - return nil, err + log.Ctx(ctx).Warn("fail to operate the privilege", zap.Any("in", entity), zap.Error(err)) + return err } - return nil, nil - })) - redoTask.AddAsyncStep(NewSimpleStep("operate privilege cache", func(ctx context.Context) ([]nestedStep, error) { + return nil + }(); err != nil { + return errors.Wrap(err, "failed to operate the privilege") + } + + if err := func() error { // set back to expand privilege group - in.Entity.Grantor.Privilege.Name = privName + entity.Grantor.Privilege.Name = privName var opType int32 - switch in.Type { + switch operateType { case milvuspb.OperatePrivilegeType_Grant: opType = int32(typeutil.CacheGrantPrivilege) case milvuspb.OperatePrivilegeType_Revoke: opType = int32(typeutil.CacheRevokePrivilege) default: - log.Ctx(ctx).Warn("invalid operate type for the OperatePrivilege api", zap.Any("in", in)) - return nil, nil + log.Ctx(ctx).Warn("invalid operate type for the OperatePrivilege api", zap.Any("operate_type", operateType)) + return errors.New("invalid operate type for the OperatePrivilege api") } - grants := []*milvuspb.GrantEntity{in.Entity} + grants := []*milvuspb.GrantEntity{entity} allGroups, err := core.getDefaultAndCustomPrivilegeGroups(ctx) if err != nil { - return nil, err + return err } groups := lo.SliceToMap(allGroups, func(group *milvuspb.PrivilegeGroupInfo) (string, []*milvuspb.PrivilegeEntity) { return group.GroupName, group.Privileges }) expandGrants, err := core.expandPrivilegeGroups(ctx, grants, groups) if err != nil { - return nil, err + return err } // if there is same grant in the other privilege groups, the grant should not be removed from the cache - if in.Type == milvuspb.OperatePrivilegeType_Revoke { + if operateType == milvuspb.OperatePrivilegeType_Revoke { metaGrants, err := core.meta.SelectGrant(ctx, util.DefaultTenant, &milvuspb.GrantEntity{ - Role: in.Entity.Role, - DbName: in.Entity.DbName, + Role: entity.Role, + DbName: entity.DbName, }) if err != nil { - return nil, err + return err } metaExpandGrants, err := core.expandPrivilegeGroups(ctx, metaGrants, groups) if err != nil { - return nil, err + return err } expandGrants = lo.Filter(expandGrants, func(g1 *milvuspb.GrantEntity, _ int) bool { return !lo.ContainsBy(metaExpandGrants, func(g2 *milvuspb.GrantEntity) bool { @@ -212,45 +105,23 @@ func executeOperatePrivilegeTaskSteps(ctx context.Context, core *Core, in *milvu OpType: opType, OpKey: funcutil.PolicyForPrivileges(expandGrants), }); err != nil { - log.Ctx(ctx).Warn("fail to refresh policy info cache", zap.Any("in", in), zap.Error(err)) - return nil, err + log.Ctx(ctx).Warn("fail to refresh policy info cache", zap.Any("in", entity), zap.Error(err)) + return err } } - return nil, nil - })) - - return redoTask.Execute(ctx) + return nil + }(); err != nil { + return errors.Wrap(err, "failed to refresh policy info cache") + } + return nil } -func executeRestoreRBACTaskSteps(ctx context.Context, core *Core, in *milvuspb.RestoreRBACMetaRequest) error { - redoTask := newBaseRedoTask(core.stepExecutor) - redoTask.AddSyncStep(NewSimpleStep("restore rbac meta data", func(ctx context.Context) ([]nestedStep, error) { - if err := core.meta.RestoreRBAC(ctx, util.DefaultTenant, in.RBACMeta); err != nil { - log.Ctx(ctx).Warn("fail to restore rbac meta data", zap.Any("in", in), zap.Error(err)) - return nil, err - } - return nil, nil - })) - redoTask.AddAsyncStep(NewSimpleStep("operate privilege cache", func(ctx context.Context) ([]nestedStep, error) { - if err := core.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ - OpType: int32(typeutil.CacheRefresh), - }); err != nil { - log.Ctx(ctx).Warn("fail to refresh policy info cache", zap.Any("in", in), zap.Error(err)) - return nil, err - } - return nil, nil - })) - - return redoTask.Execute(ctx) -} - -func executeOperatePrivilegeGroupTaskSteps(ctx context.Context, core *Core, in *milvuspb.OperatePrivilegeGroupRequest) error { - redoTask := newBaseRedoTask(core.stepExecutor) - redoTask.AddSyncStep(NewSimpleStep("operate privilege group", func(ctx context.Context) ([]nestedStep, error) { +func executeOperatePrivilegeGroupTaskSteps(ctx context.Context, core *Core, in *milvuspb.PrivilegeGroupInfo, operateType milvuspb.OperatePrivilegeGroupType) error { + if err := func() error { groups, err := core.meta.ListPrivilegeGroups(ctx) if err != nil && !common.IsIgnorableError(err) { log.Ctx(ctx).Warn("fail to list privilege groups", zap.Error(err)) - return nil, err + return err } currGroups := lo.SliceToMap(groups, func(group *milvuspb.PrivilegeGroupInfo) (string, []*milvuspb.PrivilegeEntity) { return group.GroupName, group.Privileges @@ -259,34 +130,25 @@ func executeOperatePrivilegeGroupTaskSteps(ctx context.Context, core *Core, in * // get roles granted to the group roles, err := core.meta.GetPrivilegeGroupRoles(ctx, in.GroupName) if err != nil { - return nil, err + return err } - newGroups := make(map[string][]*milvuspb.PrivilegeEntity) for k, v := range currGroups { if k != in.GroupName { newGroups[k] = v continue } - switch in.Type { + switch operateType { case milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup: newPrivs := lo.Union(v, in.Privileges) newGroups[k] = lo.UniqBy(newPrivs, func(p *milvuspb.PrivilegeEntity) string { return p.Name }) - - // check if privileges are the same privilege level - privilegeLevels := lo.SliceToMap(newPrivs, func(p *milvuspb.PrivilegeEntity) (string, struct{}) { - return util.GetPrivilegeLevel(p.Name), struct{}{} - }) - if len(privilegeLevels) > 1 { - return nil, errors.New("privileges are not the same privilege level") - } case milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup: newPrivs, _ := lo.Difference(v, in.Privileges) newGroups[k] = newPrivs default: - return nil, errors.New("invalid operate type") + return errors.New("invalid operate type") } } @@ -306,15 +168,15 @@ func executeOperatePrivilegeGroupTaskSteps(ctx context.Context, core *Core, in * DbName: util.AnyWord, }) if err != nil { - return nil, err + return err } currGrants, err := core.expandPrivilegeGroups(ctx, grants, currGroups) if err != nil { - return nil, err + return err } newGrants, err := core.expandPrivilegeGroups(ctx, grants, newGroups) if err != nil { - return nil, err + return err } toRevoke := lo.Filter(currGrants, func(item *milvuspb.GrantEntity, _ int) bool { @@ -340,7 +202,7 @@ func executeOperatePrivilegeGroupTaskSteps(ctx context.Context, core *Core, in * OpKey: funcutil.PolicyForPrivileges(rolesToRevoke), }); err != nil { log.Ctx(ctx).Warn("fail to refresh policy info cache for revoke privileges in operate privilege group", zap.Any("in", in), zap.Error(err)) - return nil, err + return err } } @@ -351,19 +213,16 @@ func executeOperatePrivilegeGroupTaskSteps(ctx context.Context, core *Core, in * OpKey: funcutil.PolicyForPrivileges(rolesToGrant), }); err != nil { log.Ctx(ctx).Warn("fail to refresh policy info cache for grants privilege in operate privilege group", zap.Any("in", in), zap.Error(err)) - return nil, err + return err } } - return nil, nil - })) - - redoTask.AddSyncStep(NewSimpleStep("operate privilege group meta data", func(ctx context.Context) ([]nestedStep, error) { - err := core.meta.OperatePrivilegeGroup(ctx, in.GroupName, in.Privileges, in.Type) - if err != nil && !common.IsIgnorableError(err) { - log.Ctx(ctx).Warn("fail to operate privilege group", zap.Error(err)) - } - return nil, err - })) - - return redoTask.Execute(ctx) + return nil + }(); err != nil { + return errors.Wrap(err, "failed to refresh policy info cache") + } + if err := core.meta.OperatePrivilegeGroup(ctx, in.GroupName, in.Privileges, operateType); err != nil && !common.IsIgnorableError(err) { + log.Ctx(ctx).Warn("fail to operate privilege group", zap.Error(err)) + return errors.Wrap(err, "failed to operate privilege group") + } + return nil } diff --git a/internal/rootcoord/root_coord.go b/internal/rootcoord/root_coord.go index c44b364df6..32fdf90660 100644 --- a/internal/rootcoord/root_coord.go +++ b/internal/rootcoord/root_coord.go @@ -62,7 +62,6 @@ import ( "github.com/milvus-io/milvus/pkg/v2/util" "github.com/milvus-io/milvus/pkg/v2/util/commonpbutil" "github.com/milvus-io/milvus/pkg/v2/util/contextutil" - "github.com/milvus-io/milvus/pkg/v2/util/crypto" "github.com/milvus-io/milvus/pkg/v2/util/expr" "github.com/milvus-io/milvus/pkg/v2/util/funcutil" "github.com/milvus-io/milvus/pkg/v2/util/merr" @@ -488,6 +487,7 @@ func (c *Core) Init() error { c.initOnce.Do(func() { initError = c.initInternal() + RegisterDDLCallbacks(c) }) log.Info("RootCoord init successfully") @@ -495,22 +495,7 @@ func (c *Core) Init() error { } func (c *Core) initCredentials(initCtx context.Context) error { - credInfo, _ := c.meta.GetCredential(initCtx, util.UserRoot) - if credInfo == nil { - encryptedRootPassword, err := crypto.PasswordEncrypt(Params.CommonCfg.DefaultRootPassword.GetValue()) - if err != nil { - log.Ctx(initCtx).Warn("RootCoord init user root failed", zap.Error(err)) - return err - } - log.Ctx(initCtx).Info("RootCoord init user root") - err = c.meta.AddCredential(initCtx, &internalpb.CredentialInfo{Username: util.UserRoot, EncryptedPassword: encryptedRootPassword}) - if err != nil { - log.Ctx(initCtx).Warn("RootCoord init user root failed", zap.Error(err)) - return err - } - return nil - } - return nil + return c.meta.InitCredential(initCtx) } func (c *Core) initRbac(initCtx context.Context) error { @@ -531,7 +516,7 @@ func (c *Core) initRbac(initCtx context.Context) error { } if Params.RoleCfg.Enabled.GetAsBool() { - return c.initBuiltinRoles() + return c.initBuiltinRoles(initCtx) } return nil } @@ -580,22 +565,22 @@ func (c *Core) initPublicRolePrivilege(initCtx context.Context) error { return nil } -func (c *Core) initBuiltinRoles() error { - log := log.Ctx(c.ctx) +func (c *Core) initBuiltinRoles(ctx context.Context) error { + log := log.Ctx(ctx) rolePrivilegesMap := Params.RoleCfg.Roles.GetAsRoleDetails() for role, privilegesJSON := range rolePrivilegesMap { - err := c.meta.CreateRole(c.ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: role}) + err := c.meta.CreateRole(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: role}) if err != nil && !common.IsIgnorableError(err) { log.Error("create a builtin role fail", zap.String("roleName", role), zap.Error(err)) return errors.Wrapf(err, "failed to create a builtin role: %s", role) } for _, privilege := range privilegesJSON[util.RoleConfigPrivileges] { - privilegeName, err := c.getMetastorePrivilegeName(c.ctx, privilege[util.RoleConfigPrivilege]) + privilegeName, err := c.getMetastorePrivilegeName(ctx, privilege[util.RoleConfigPrivilege]) if err != nil { return errors.Wrapf(err, "failed to get metastore privilege name for: %s", privilege[util.RoleConfigPrivilege]) } - err = c.meta.OperatePrivilege(c.ctx, util.DefaultTenant, &milvuspb.GrantEntity{ + err = c.meta.OperatePrivilege(ctx, util.DefaultTenant, &milvuspb.GrantEntity{ Role: &milvuspb.RoleEntity{Name: role}, Object: &milvuspb.ObjectEntity{Name: privilege[util.RoleConfigObjectType]}, ObjectName: privilege[util.RoleConfigObjectName], @@ -2193,20 +2178,14 @@ func (c *Core) CreateCredential(ctx context.Context, credInfo *internalpb.Creden return merr.Status(err), nil } - // insert to db - err := c.meta.AddCredential(ctx, credInfo) - if err != nil { - ctxLog.Warn("CreateCredential save credential failed", zap.Error(err)) + if err := c.broadcastAlterUserForCreateCredential(ctx, credInfo); err != nil { + ctxLog.Warn("CreateCredential failed", zap.Error(err)) metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.FailLabel).Inc() + if errors.Is(err, errUserAlreadyExists) { + return merr.StatusWithErrorCode(errors.Errorf("user already exists: %s", credInfo.Username), commonpb.ErrorCode_CreateCredentialFailure), nil + } return merr.StatusWithErrorCode(err, commonpb.ErrorCode_CreateCredentialFailure), nil } - // update proxy's local cache - err = c.UpdateCredCache(ctx, credInfo) - if err != nil { - ctxLog.Warn("CreateCredential add cache failed", zap.Error(err)) - metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.FailLabel).Inc() - } - ctxLog.Debug("CreateCredential success") metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) @@ -2254,21 +2233,12 @@ func (c *Core) UpdateCredential(ctx context.Context, credInfo *internalpb.Creden if err := merr.CheckHealthy(c.GetStateCode()); err != nil { return merr.Status(err), nil } - // update data on storage - err := c.meta.AlterCredential(ctx, credInfo) - if err != nil { - ctxLog.Warn("UpdateCredential save credential failed", zap.Error(err)) + + if err := c.broadcastAlterUserForUpdateCredential(ctx, credInfo); err != nil { + ctxLog.Warn("UpdateCredential append message failed", zap.Error(err)) metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.FailLabel).Inc() return merr.StatusWithErrorCode(err, commonpb.ErrorCode_UpdateCredentialFailure), nil } - // update proxy's local cache - err = c.UpdateCredCache(ctx, credInfo) - if err != nil { - ctxLog.Warn("UpdateCredential update cache failed", zap.Error(err)) - metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.FailLabel).Inc() - return merr.StatusWithErrorCode(err, commonpb.ErrorCode_UpdateCredentialFailure), nil - } - ctxLog.Debug("UpdateCredential success") metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) @@ -2285,27 +2255,25 @@ func (c *Core) DeleteCredential(ctx context.Context, in *milvuspb.DeleteCredenti if err := merr.CheckHealthy(c.GetStateCode()); err != nil { return merr.Status(err), nil } - var status *commonpb.Status - defer func() { - if status.Code != 0 { - metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.FailLabel).Inc() + + // user not found will not report to the client to achieve idempotent + if err := c.broadcastDropUserForDeleteCredential(ctx, in); err != nil { + if errors.Is(err, errUserNotFound) { + ctxLog.Info("DeleteCredential user not found, ignored") + metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() + metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) + return merr.Success(), nil } - }() - - err := executeDeleteCredentialTaskSteps(ctx, c, in.Username) - if err != nil { - errMsg := "fail to execute task when deleting the user" - ctxLog.Warn(errMsg, zap.Error(err)) - status = merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_DeleteCredentialFailure) - return status, nil + ctxLog.Warn("DeleteCredential append message failed", zap.Error(err)) + metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.FailLabel).Inc() + return merr.StatusWithErrorCode(err, commonpb.ErrorCode_DeleteCredentialFailure), nil } - ctxLog.Debug("DeleteCredential success") + ctxLog.Debug("DeleteCredential success") metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) metrics.RootCoordNumOfCredentials.Dec() - status = merr.Success() - return status, nil + return merr.Success(), nil } // ListCredUsers list all usernames @@ -2352,12 +2320,13 @@ func (c *Core) CreateRole(ctx context.Context, in *milvuspb.CreateRoleRequest) ( if err := merr.CheckHealthy(c.GetStateCode()); err != nil { return merr.Status(err), nil } - entity := in.Entity - err := c.meta.CreateRole(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: entity.Name}) - if err != nil { - errMsg := "fail to create role" - ctxLog.Warn(errMsg, zap.Error(err)) + if err := c.broadcastCreateRole(ctx, in); err != nil { + ctxLog.Warn("fail to create role", zap.Error(err)) + metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.FailLabel).Inc() + if errors.Is(err, errRoleAlreadyExists) { + return merr.StatusWithErrorCode(errors.Newf("role [%s] already exists", in.GetEntity()), commonpb.ErrorCode_CreateRoleFailure), nil + } return merr.StatusWithErrorCode(err, commonpb.ErrorCode_CreateRoleFailure), nil } @@ -2365,7 +2334,6 @@ func (c *Core) CreateRole(ctx context.Context, in *milvuspb.CreateRoleRequest) ( metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) metrics.RootCoordNumOfRoles.Inc() - return merr.Success(), nil } @@ -2386,32 +2354,13 @@ func (c *Core) DropRole(ctx context.Context, in *milvuspb.DropRoleRequest) (*com if err := merr.CheckHealthy(c.GetStateCode()); err != nil { return merr.Status(err), nil } - for util.IsBuiltinRole(in.GetRoleName()) { - err := merr.WrapErrPrivilegeNotPermitted("the role[%s] is a builtin role, which can't be dropped", in.GetRoleName()) - return merr.Status(err), nil - } - if _, err := c.meta.SelectRole(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: in.RoleName}, false); err != nil { - errMsg := "not found the role, maybe the role isn't existed or internal system error" - ctxLog.Warn(errMsg, zap.Error(err)) - return merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_DropRoleFailure), nil - } - if !in.ForceDrop { - grantEntities, err := c.meta.SelectGrant(ctx, util.DefaultTenant, &milvuspb.GrantEntity{ - Role: &milvuspb.RoleEntity{Name: in.RoleName}, - DbName: "*", - }) - if len(grantEntities) != 0 { - errMsg := "fail to drop the role that it has privileges. Use REVOKE API to revoke privileges" - ctxLog.Warn(errMsg, zap.Any("grants", grantEntities), zap.Error(err)) - return merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_DropRoleFailure), nil + if err := c.broadcastDropRole(ctx, in); err != nil { + ctxLog.Warn("fail to drop role", zap.Error(err)) + if errors.Is(err, errRoleNotExists) { + return merr.StatusWithErrorCode(errors.New("not found the role, maybe the role isn't existed or internal system error"), commonpb.ErrorCode_DropRoleFailure), nil } - } - err := executeDropRoleTaskSteps(ctx, c, in.RoleName, in.ForceDrop) - if err != nil { - errMsg := "fail to execute task when dropping the role" - ctxLog.Warn(errMsg, zap.Error(err)) - return merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_DropRoleFailure), nil + return merr.StatusWithErrorCode(err, commonpb.ErrorCode_DropRoleFailure), nil } ctxLog.Debug(method+" success", zap.String("role_name", in.RoleName)) @@ -2438,27 +2387,15 @@ func (c *Core) OperateUserRole(ctx context.Context, in *milvuspb.OperateUserRole return merr.Status(err), nil } - if _, err := c.meta.SelectRole(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: in.RoleName}, false); err != nil { - errMsg := "not found the role, maybe the role isn't existed or internal system error" - ctxLog.Warn(errMsg, zap.Error(err)) - return merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_OperateUserRoleFailure), nil - } - if in.Type != milvuspb.OperateUserRoleType_RemoveUserFromRole { - if _, err := c.meta.SelectUser(ctx, util.DefaultTenant, &milvuspb.UserEntity{Name: in.Username}, false); err != nil { - errMsg := "not found the user, maybe the user isn't existed or internal system error" - ctxLog.Warn(errMsg, zap.Error(err)) - return merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_OperateUserRoleFailure), nil + if err := c.broadcastOperateUserRole(ctx, in); err != nil { + ctxLog.Warn("fail to operate the user and role", zap.Error(err)) + if errors.Is(err, errRoleNotExists) { + return merr.StatusWithErrorCode(errors.New("not found the role, maybe the role isn't existed or internal system error"), commonpb.ErrorCode_OperateUserRoleFailure), nil } + return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperateUserRoleFailure), nil } - err := executeOperateUserRoleTaskSteps(ctx, c, in) - if err != nil { - errMsg := "fail to execute task when operate the user and role" - ctxLog.Warn(errMsg, zap.Error(err)) - return merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_OperateUserRoleFailure), nil - } - - ctxLog.Debug(method + " success") + ctxLog.Info(method + " success") metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) return merr.Success(), nil @@ -2558,14 +2495,14 @@ func (c *Core) SelectUser(ctx context.Context, in *milvuspb.SelectUserRequest) ( }, nil } -func (c *Core) isValidRole(entity *milvuspb.RoleEntity) error { +func (c *Core) isValidRole(ctx context.Context, entity *milvuspb.RoleEntity) error { if entity == nil { return errors.New("the role entity is nil") } if entity.Name == "" { return errors.New("the name in the role entity is empty") } - if _, err := c.meta.SelectRole(c.ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: entity.Name}, false); err != nil { + if _, err := c.meta.SelectRole(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: entity.Name}, false); err != nil { log.Warn("fail to select the role", zap.String("role_name", entity.Name), zap.Error(err)) return errors.New("not found the role, maybe the role isn't existed or internal system error") } @@ -2642,36 +2579,11 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile ctxLog := log.Ctx(ctx).With(zap.String("role", typeutil.RootCoordRole), zap.Any("in", in)) ctxLog.Debug(method) - if err := c.operatePrivilegeCommonCheck(ctx, in); err != nil { - return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil + if err := merr.CheckHealthy(c.GetStateCode()); err != nil { + return merr.Status(err), nil } - privName := in.Entity.Grantor.Privilege.Name - switch in.Version { - case "v2": - if err := c.isValidPrivilegeV2(ctx, privName); err != nil { - ctxLog.Error("", zap.Error(err)) - return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil - } - if err := c.validatePrivilegeGroupParams(ctx, privName, in.Entity.DbName, in.Entity.ObjectName); err != nil { - ctxLog.Error("", zap.Error(err)) - return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil - } - // set up object type for metastore, to be compatible with v1 version - in.Entity.Object.Name = util.GetObjectType(privName) - default: - if err := c.isValidPrivilege(ctx, privName, in.Entity.Object.Name); err != nil { - ctxLog.Error("", zap.Error(err)) - return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil - } - // set up object name if it is global object type and not built in privilege group - if in.Entity.Object.Name == commonpb.ObjectType_Global.String() && !util.IsBuiltinPrivilegeGroup(in.Entity.Grantor.Privilege.Name) { - in.Entity.ObjectName = util.AnyWord - } - } - - err := executeOperatePrivilegeTaskSteps(ctx, c, in) - if err != nil { + if err := c.broadcastOperatePrivilege(ctx, in); err != nil { errMsg := "fail to execute task when operating the privilege" ctxLog.Warn(errMsg, zap.Error(err)) return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil @@ -2684,9 +2596,6 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile } func (c *Core) operatePrivilegeCommonCheck(ctx context.Context, in *milvuspb.OperatePrivilegeRequest) error { - if err := merr.CheckHealthy(c.GetStateCode()); err != nil { - return err - } if in.Type != milvuspb.OperatePrivilegeType_Grant && in.Type != milvuspb.OperatePrivilegeType_Revoke { errMsg := fmt.Sprintf("invalid operate privilege type, current type: %s, valid value: [%s, %s]", in.Type, milvuspb.OperatePrivilegeType_Grant, milvuspb.OperatePrivilegeType_Revoke) return errors.New(errMsg) @@ -2698,7 +2607,7 @@ func (c *Core) operatePrivilegeCommonCheck(ctx context.Context, in *milvuspb.Ope if err := c.isValidObject(in.Entity.Object); err != nil { return errors.New("the object entity in the request is nil or invalid") } - if err := c.isValidRole(in.Entity.Role); err != nil { + if err := c.isValidRole(ctx, in.Entity.Role); err != nil { return err } entity := in.Entity.Grantor @@ -2798,7 +2707,7 @@ func (c *Core) SelectGrant(ctx context.Context, in *milvuspb.SelectGrantRequest) Status: merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_SelectGrantFailure), }, nil } - if err := c.isValidRole(in.Entity.Role); err != nil { + if err := c.isValidRole(ctx, in.Entity.Role); err != nil { ctxLog.Warn("", zap.Error(err)) return &milvuspb.SelectGrantResponse{ Status: merr.StatusWithErrorCode(err, commonpb.ErrorCode_SelectGrantFailure), @@ -2939,8 +2848,11 @@ func (c *Core) RestoreRBAC(ctx context.Context, in *milvuspb.RestoreRBACMetaRequ return merr.Status(err), nil } - err := executeRestoreRBACTaskSteps(ctx, c, in) - if err != nil { + if err := c.broadcastRestoreRBACV2(ctx, in); err != nil { + if errors.Is(err, errEmptyRBACMeta) { + ctxLog.Info("restoring rbac meta is empty, skip restore", zap.Error(err)) + return merr.Success(), nil + } errMsg := "fail to execute task when restore rbac meta data" ctxLog.Warn(errMsg, zap.Error(err)) return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeFailure), nil @@ -3092,12 +3004,12 @@ func (c *Core) CreatePrivilegeGroup(ctx context.Context, in *milvuspb.CreatePriv return merr.StatusWithErrorCode(err, commonpb.ErrorCode_CreatePrivilegeGroupFailure), nil } - if err := c.meta.CreatePrivilegeGroup(ctx, in.GroupName); err != nil { + if err := c.broadcastCreatePrivilegeGroup(ctx, in); err != nil { ctxLog.Warn("fail to create privilege group", zap.Error(err)) return merr.StatusWithErrorCode(err, commonpb.ErrorCode_CreatePrivilegeGroupFailure), nil } - ctxLog.Debug(method + " success") + ctxLog.Info(method + " success") metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) metrics.RootCoordNumOfPrivilegeGroups.Inc() @@ -3115,12 +3027,16 @@ func (c *Core) DropPrivilegeGroup(ctx context.Context, in *milvuspb.DropPrivileg return merr.StatusWithErrorCode(err, commonpb.ErrorCode_DropPrivilegeGroupFailure), nil } - if err := c.meta.DropPrivilegeGroup(ctx, in.GroupName); err != nil { + if err := c.broadcastDropPrivilegeGroup(ctx, in); err != nil { + if errors.Is(err, errNotCustomPrivilegeGroup) { + ctxLog.Info("privilege group is not custom privilege group, skip drop", zap.Error(err)) + return merr.Success(), nil + } ctxLog.Warn("fail to drop privilege group", zap.Error(err)) return merr.StatusWithErrorCode(err, commonpb.ErrorCode_DropPrivilegeGroupFailure), nil } - ctxLog.Debug(method + " success") + ctxLog.Info(method + " success") metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.SuccessLabel).Inc() metrics.RootCoordDDLReqLatency.WithLabelValues(method).Observe(float64(tr.ElapseSpan().Milliseconds())) metrics.RootCoordNumOfPrivilegeGroups.Desc() @@ -3171,8 +3087,7 @@ func (c *Core) OperatePrivilegeGroup(ctx context.Context, in *milvuspb.OperatePr return merr.Status(err), nil } - err := executeOperatePrivilegeGroupTaskSteps(ctx, c, in) - if err != nil { + if err := c.broadcastOperatePrivilegeGroup(ctx, in); err != nil { errMsg := "fail to execute task when operate privilege group" ctxLog.Warn(errMsg, zap.Error(err)) return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperatePrivilegeGroupFailure), nil diff --git a/internal/rootcoord/root_coord_test.go b/internal/rootcoord/root_coord_test.go index a580dd8cb9..23f612c552 100644 --- a/internal/rootcoord/root_coord_test.go +++ b/internal/rootcoord/root_coord_test.go @@ -30,15 +30,22 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v2/commonpb" "github.com/milvus-io/milvus-proto/go-api/v2/milvuspb" + "github.com/milvus-io/milvus/internal/distributed/streaming" "github.com/milvus-io/milvus/internal/metastore/model" + "github.com/milvus-io/milvus/internal/mocks/distributed/mock_streaming" + "github.com/milvus-io/milvus/internal/mocks/streamingcoord/server/mock_broadcaster" mockrootcoord "github.com/milvus-io/milvus/internal/rootcoord/mocks" - "github.com/milvus-io/milvus/internal/util/proxyutil" + "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/broadcast" + "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/registry" "github.com/milvus-io/milvus/internal/util/sessionutil" "github.com/milvus-io/milvus/pkg/v2/common" "github.com/milvus-io/milvus/pkg/v2/proto/etcdpb" "github.com/milvus-io/milvus/pkg/v2/proto/internalpb" "github.com/milvus-io/milvus/pkg/v2/proto/proxypb" "github.com/milvus-io/milvus/pkg/v2/proto/rootcoordpb" + "github.com/milvus-io/milvus/pkg/v2/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v2/streaming/util/types" + "github.com/milvus-io/milvus/pkg/v2/streaming/walimpls/impls/walimplstest" "github.com/milvus-io/milvus/pkg/v2/util" "github.com/milvus-io/milvus/pkg/v2/util/funcutil" "github.com/milvus-io/milvus/pkg/v2/util/merr" @@ -51,15 +58,39 @@ import ( func TestMain(m *testing.M) { paramtable.Init() rand.Seed(time.Now().UnixNano()) - parameters := []string{"tikv", "etcd"} - var code int - for _, v := range parameters { - paramtable.Get().Save(paramtable.Get().MetaStoreCfg.MetaStoreType.Key, v) - code = m.Run() - } + code := m.Run() os.Exit(code) } +func initStreamingSystem() { + t := common.NewEmptyMockT() + wal := mock_streaming.NewMockWALAccesser(t) + wal.EXPECT().ControlChannel().Return(funcutil.GetControlChannel("by-dev-rootcoord-dml_0")) + streaming.SetWALForTest(wal) + + bapi := mock_broadcaster.NewMockBroadcastAPI(t) + bapi.EXPECT().Broadcast(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, msg message.BroadcastMutableMessage) (*types.BroadcastAppendResult, error) { + results := make(map[string]*message.AppendResult) + for _, vchannel := range msg.BroadcastHeader().VChannels { + results[vchannel] = &message.AppendResult{ + MessageID: walimplstest.NewTestMessageID(1), + TimeTick: tsoutil.ComposeTSByTime(time.Now(), 0), + LastConfirmedMessageID: walimplstest.NewTestMessageID(1), + } + } + registry.CallMessageAckCallback(context.Background(), msg, results) + return &types.BroadcastAppendResult{}, nil + }) + bapi.EXPECT().Close().Return() + + mb := mock_broadcaster.NewMockBroadcaster(t) + mb.EXPECT().WithResourceKeys(mock.Anything, mock.Anything).Return(bapi, nil) + mb.EXPECT().Close().Return() + broadcast.Release() + broadcast.ResetBroadcaster() + broadcast.Register(mb) +} + func TestRootCoord_CreateDatabase(t *testing.T) { t.Run("not healthy", func(t *testing.T) { c := newTestCore(withAbnormalCode()) @@ -1733,345 +1764,6 @@ func TestRootCoord_DescribeDatabase(t *testing.T) { }) } -func TestRootCoord_RBACError(t *testing.T) { - ctx := context.Background() - c := newTestCore(withHealthyCode(), withInvalidMeta()) - t.Run("create credential failed", func(t *testing.T) { - resp, err := c.CreateCredential(ctx, &internalpb.CredentialInfo{Username: "foo", EncryptedPassword: "bar"}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - }) - t.Run("get credential failed", func(t *testing.T) { - resp, err := c.GetCredential(ctx, &rootcoordpb.GetCredentialRequest{Username: "foo"}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - }) - t.Run("update credential failed", func(t *testing.T) { - resp, err := c.UpdateCredential(ctx, &internalpb.CredentialInfo{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - }) - t.Run("delete credential failed", func(t *testing.T) { - resp, err := c.DeleteCredential(ctx, &milvuspb.DeleteCredentialRequest{Username: "foo"}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - }) - t.Run("list credential failed", func(t *testing.T) { - resp, err := c.ListCredUsers(ctx, &milvuspb.ListCredUsersRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - }) - t.Run("create role failed", func(t *testing.T) { - resp, err := c.CreateRole(ctx, &milvuspb.CreateRoleRequest{Entity: &milvuspb.RoleEntity{Name: "foo"}}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - }) - t.Run("drop role failed", func(t *testing.T) { - resp, err := c.DropRole(ctx, &milvuspb.DropRoleRequest{RoleName: "foo"}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - }) - t.Run("operate user role failed", func(t *testing.T) { - mockMeta := c.meta.(*mockMetaTable) - mockMeta.SelectRoleFunc = func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) { - return nil, nil - } - mockMeta.SelectUserFunc = func(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) { - return nil, nil - } - resp, err := c.OperateUserRole(ctx, &milvuspb.OperateUserRoleRequest{RoleName: "foo", Username: "bar", Type: milvuspb.OperateUserRoleType_AddUserToRole}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - mockMeta.SelectRoleFunc = func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) { - return nil, errors.New("mock error") - } - mockMeta.SelectUserFunc = func(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) { - return nil, errors.New("mock error") - } - }) - t.Run("select role failed", func(t *testing.T) { - { - resp, err := c.SelectRole(ctx, &milvuspb.SelectRoleRequest{Role: &milvuspb.RoleEntity{Name: "foo"}}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - } - { - resp, err := c.SelectRole(ctx, &milvuspb.SelectRoleRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - } - }) - t.Run("select user failed", func(t *testing.T) { - { - resp, err := c.SelectUser(ctx, &milvuspb.SelectUserRequest{User: &milvuspb.UserEntity{Name: "foo"}}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - } - { - resp, err := c.SelectUser(ctx, &milvuspb.SelectUserRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - } - }) - t.Run("operate privilege failed", func(t *testing.T) { - { - resp, err := c.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{Type: milvuspb.OperatePrivilegeType(100)}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - } - { - resp, err := c.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{Type: milvuspb.OperatePrivilegeType_Grant}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - } - { - resp, err := c.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{Entity: &milvuspb.GrantEntity{Object: &milvuspb.ObjectEntity{Name: "CollectionErr"}}}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - } - { - resp, err := c.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{Entity: &milvuspb.GrantEntity{Object: &milvuspb.ObjectEntity{Name: "Collection"}, Role: &milvuspb.RoleEntity{Name: "foo"}}}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - } - - mockMeta := c.meta.(*mockMetaTable) - mockMeta.SelectRoleFunc = func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) { - return nil, nil - } - mockMeta.ListPrivilegeGroupsFunc = func(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error) { - return nil, nil - } - { - resp, err := c.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{Entity: &milvuspb.GrantEntity{ - Role: &milvuspb.RoleEntity{Name: "foo"}, - Object: &milvuspb.ObjectEntity{Name: "Collection"}, - ObjectName: "col1", - Grantor: &milvuspb.GrantorEntity{ - User: &milvuspb.UserEntity{Name: "root"}, - Privilege: &milvuspb.PrivilegeEntity{Name: "Insert"}, - }, - }, Type: milvuspb.OperatePrivilegeType_Grant}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - } - mockMeta.IsCustomPrivilegeGroupFunc = func(ctx context.Context, groupName string) (bool, error) { - return false, nil - } - mockMeta.SelectUserFunc = func(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) { - return nil, nil - } - resp, err := c.OperatePrivilege(ctx, &milvuspb.OperatePrivilegeRequest{Entity: &milvuspb.GrantEntity{ - Role: &milvuspb.RoleEntity{Name: "foo"}, - Object: &milvuspb.ObjectEntity{Name: "Collection"}, - ObjectName: "col1", - Grantor: &milvuspb.GrantorEntity{ - User: &milvuspb.UserEntity{Name: "root"}, - Privilege: &milvuspb.PrivilegeEntity{Name: "Insert"}, - }, - }, Type: milvuspb.OperatePrivilegeType_Grant}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode) - mockMeta.SelectRoleFunc = func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) { - return nil, errors.New("mock error") - } - mockMeta.SelectUserFunc = func(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) { - return nil, errors.New("mock error") - } - }) - - t.Run("operate privilege group failed", func(t *testing.T) { - mockMeta := c.meta.(*mockMetaTable) - mockMeta.ListPrivilegeGroupsFunc = func(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error) { - return nil, errors.New("mock error") - } - mockMeta.CreatePrivilegeGroupFunc = func(ctx context.Context, groupName string) error { - return errors.New("mock error") - } - mockMeta.GetPrivilegeGroupRolesFunc = func(ctx context.Context, groupName string) ([]*milvuspb.RoleEntity, error) { - return nil, errors.New("mock error") - } - { - resp, err := c.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetErrorCode()) - } - { - resp, err := c.ListPrivilegeGroups(ctx, &milvuspb.ListPrivilegeGroupsRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - } - { - resp, err := c.OperatePrivilegeGroup(ctx, &milvuspb.OperatePrivilegeGroupRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetErrorCode()) - } - { - resp, err := c.CreatePrivilegeGroup(ctx, &milvuspb.CreatePrivilegeGroupRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetErrorCode()) - } - }) - - t.Run("select grant failed", func(t *testing.T) { - { - resp, err := c.SelectGrant(ctx, &milvuspb.SelectGrantRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - } - { - resp, err := c.SelectGrant(ctx, &milvuspb.SelectGrantRequest{Entity: &milvuspb.GrantEntity{Role: &milvuspb.RoleEntity{Name: "foo"}}}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - } - mockMeta := c.meta.(*mockMetaTable) - mockMeta.SelectRoleFunc = func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) { - return nil, nil - } - { - resp, err := c.SelectGrant(ctx, &milvuspb.SelectGrantRequest{Entity: &milvuspb.GrantEntity{Role: &milvuspb.RoleEntity{Name: "foo"}, Object: &milvuspb.ObjectEntity{Name: "CollectionFoo"}}}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - } - { - resp, err := c.SelectGrant(ctx, &milvuspb.SelectGrantRequest{Entity: &milvuspb.GrantEntity{Role: &milvuspb.RoleEntity{Name: "foo"}, Object: &milvuspb.ObjectEntity{Name: "Collection"}}}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - } - mockMeta.SelectRoleFunc = func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) { - return nil, errors.New("mock error") - } - }) - - t.Run("select grant success", func(t *testing.T) { - mockMeta := c.meta.(*mockMetaTable) - mockMeta.SelectRoleFunc = func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) { - return []*milvuspb.RoleResult{ - { - Role: &milvuspb.RoleEntity{Name: "foo"}, - }, - }, nil - } - mockMeta.SelectGrantFunc = func(ctx context.Context, tenant string, entity *milvuspb.GrantEntity) ([]*milvuspb.GrantEntity, error) { - return []*milvuspb.GrantEntity{ - { - Role: &milvuspb.RoleEntity{Name: "foo"}, - }, - }, merr.ErrIoKeyNotFound - } - - { - resp, err := c.SelectGrant(ctx, &milvuspb.SelectGrantRequest{Entity: &milvuspb.GrantEntity{Role: &milvuspb.RoleEntity{Name: "foo"}, Object: &milvuspb.ObjectEntity{Name: "Collection"}, ObjectName: "fir"}}) - assert.NoError(t, err) - assert.Equal(t, 1, len(resp.GetEntities())) - assert.Equal(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - } - - mockMeta.SelectRoleFunc = func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) { - return nil, errors.New("mock error") - } - - mockMeta.SelectGrantFunc = func(ctx context.Context, tenant string, entity *milvuspb.GrantEntity) ([]*milvuspb.GrantEntity, error) { - return nil, errors.New("mock error") - } - }) - - t.Run("list policy failed", func(t *testing.T) { - resp, err := c.ListPolicy(ctx, &internalpb.ListPolicyRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - - mockMeta := c.meta.(*mockMetaTable) - mockMeta.ListPolicyFunc = func(ctx context.Context, tenant string) ([]*milvuspb.GrantEntity, error) { - return []*milvuspb.GrantEntity{{ - ObjectName: "*", - Object: &milvuspb.ObjectEntity{ - Name: "Global", - }, - Role: &milvuspb.RoleEntity{Name: "role"}, - Grantor: &milvuspb.GrantorEntity{Privilege: &milvuspb.PrivilegeEntity{Name: "CustomGroup"}}, - }}, nil - } - resp, err = c.ListPolicy(ctx, &internalpb.ListPolicyRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - mockMeta.ListPrivilegeGroupsFunc = func(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error) { - return []*milvuspb.PrivilegeGroupInfo{ - { - GroupName: "CollectionAdmin", - Privileges: []*milvuspb.PrivilegeEntity{{Name: "CreateCollection"}}, - }, - }, nil - } - resp, err = c.ListPolicy(ctx, &internalpb.ListPolicyRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - mockMeta.IsCustomPrivilegeGroupFunc = func(ctx context.Context, groupName string) (bool, error) { - return true, nil - } - resp, err = c.ListPolicy(ctx, &internalpb.ListPolicyRequest{}) - assert.NoError(t, err) - assert.NotEqual(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode()) - mockMeta.ListPolicyFunc = func(ctx context.Context, tenant string) ([]*milvuspb.GrantEntity, error) { - return []*milvuspb.GrantEntity{}, errors.New("mock error") - } - mockMeta.ListPrivilegeGroupsFunc = func(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error) { - return []*milvuspb.PrivilegeGroupInfo{}, errors.New("mock error") - } - mockMeta.IsCustomPrivilegeGroupFunc = func(ctx context.Context, groupName string) (bool, error) { - return false, errors.New("mock error") - } - }) -} - -func TestRootCoord_BuiltinRoles(t *testing.T) { - roleDbAdmin := "db_admin" - paramtable.Init() - paramtable.Get().Save(paramtable.Get().RoleCfg.Enabled.Key, "true") - paramtable.Get().Save(paramtable.Get().RoleCfg.Roles.Key, `{"`+roleDbAdmin+`": {"privileges": [{"object_type": "Global", "object_name": "*", "privilege": "CreateCollection", "db_name": "*"}]}}`) - t.Run("init builtin roles success", func(t *testing.T) { - c := newTestCore(withHealthyCode(), withInvalidMeta()) - mockMeta := c.meta.(*mockMetaTable) - mockMeta.CreateRoleFunc = func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error { - return nil - } - mockMeta.OperatePrivilegeFunc = func(ctx context.Context, tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error { - return nil - } - mockMeta.ListPrivilegeGroupsFunc = func(ctx context.Context) ([]*milvuspb.PrivilegeGroupInfo, error) { - return nil, nil - } - err := c.initBuiltinRoles() - assert.Equal(t, nil, err) - assert.True(t, util.IsBuiltinRole(roleDbAdmin)) - assert.False(t, util.IsBuiltinRole(util.RoleAdmin)) - resp, err := c.DropRole(context.Background(), &milvuspb.DropRoleRequest{RoleName: roleDbAdmin}) - assert.Equal(t, nil, err) - assert.Equal(t, int32(1401), resp.Code) // merr.ErrPrivilegeNotPermitted - }) - t.Run("init builtin roles fail to create role", func(t *testing.T) { - c := newTestCore(withHealthyCode(), withInvalidMeta()) - mockMeta := c.meta.(*mockMetaTable) - mockMeta.CreateRoleFunc = func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error { - return merr.ErrPrivilegeNotPermitted - } - err := c.initBuiltinRoles() - assert.Error(t, err) - }) - t.Run("init builtin roles fail to operate privileg", func(t *testing.T) { - c := newTestCore(withHealthyCode(), withInvalidMeta()) - mockMeta := c.meta.(*mockMetaTable) - mockMeta.CreateRoleFunc = func(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error { - return nil - } - mockMeta.OperatePrivilegeFunc = func(ctx context.Context, tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error { - return merr.ErrPrivilegeNotPermitted - } - err := c.initBuiltinRoles() - assert.Error(t, err) - }) -} - func TestCore_Stop(t *testing.T) { t.Run("abnormal stop before component is ready", func(t *testing.T) { c := &Core{} @@ -2171,25 +1863,6 @@ func TestCore_BackupRBAC(t *testing.T) { assert.False(t, merr.Ok(resp.GetStatus())) } -func TestCore_RestoreRBAC(t *testing.T) { - meta := mockrootcoord.NewIMetaTable(t) - c := newTestCore(withHealthyCode(), withMeta(meta)) - mockProxyClientManager := proxyutil.NewMockProxyClientManager(t) - mockProxyClientManager.EXPECT().RefreshPolicyInfoCache(mock.Anything, mock.Anything).Return(nil).Maybe() - c.proxyClientManager = mockProxyClientManager - - meta.EXPECT().RestoreRBAC(mock.Anything, mock.Anything, mock.Anything).Return(nil) - resp, err := c.RestoreRBAC(context.Background(), &milvuspb.RestoreRBACMetaRequest{}) - assert.NoError(t, err) - assert.True(t, merr.Ok(resp)) - - meta.ExpectedCalls = nil - meta.EXPECT().RestoreRBAC(mock.Anything, mock.Anything, mock.Anything).Return(errors.New("mock error")) - resp, err = c.RestoreRBAC(context.Background(), &milvuspb.RestoreRBACMetaRequest{}) - assert.NoError(t, err) - assert.False(t, merr.Ok(resp)) -} - func TestCore_getMetastorePrivilegeName(t *testing.T) { meta := mockrootcoord.NewIMetaTable(t) c := newTestCore(withHealthyCode(), withMeta(meta)) diff --git a/pkg/proto/internal.proto b/pkg/proto/internal.proto index 282e633431..ba67081637 100644 --- a/pkg/proto/internal.proto +++ b/pkg/proto/internal.proto @@ -259,13 +259,13 @@ message ChannelTimeTickMsg { } message CredentialInfo { - string username = 1; + string username = 1; // not save in metadata. // encrypted by bcrypt (for higher security level) string encrypted_password = 2; - string tenant = 3; - bool is_super = 4; + string tenant = 3 [deprecated=true]; // not used. + bool is_super = 4 [deprecated=true]; // not used. // encrypted by sha256 (for good performance in cache mapping) - string sha256_password = 5; + string sha256_password = 5; // not save in metadata. uint64 time_tick = 6; // the timetick in wal which the credential updates } diff --git a/pkg/proto/internalpb/internal.pb.go b/pkg/proto/internalpb/internal.pb.go index 0692549a37..87c9a3006f 100644 --- a/pkg/proto/internalpb/internal.pb.go +++ b/pkg/proto/internalpb/internal.pb.go @@ -2470,14 +2470,16 @@ type CredentialInfo struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` // not save in metadata. // encrypted by bcrypt (for higher security level) EncryptedPassword string `protobuf:"bytes,2,opt,name=encrypted_password,json=encryptedPassword,proto3" json:"encrypted_password,omitempty"` - Tenant string `protobuf:"bytes,3,opt,name=tenant,proto3" json:"tenant,omitempty"` - IsSuper bool `protobuf:"varint,4,opt,name=is_super,json=isSuper,proto3" json:"is_super,omitempty"` + // Deprecated: Marked as deprecated in internal.proto. + Tenant string `protobuf:"bytes,3,opt,name=tenant,proto3" json:"tenant,omitempty"` // not used. + // Deprecated: Marked as deprecated in internal.proto. + IsSuper bool `protobuf:"varint,4,opt,name=is_super,json=isSuper,proto3" json:"is_super,omitempty"` // not used. // encrypted by sha256 (for good performance in cache mapping) - Sha256Password string `protobuf:"bytes,5,opt,name=sha256_password,json=sha256Password,proto3" json:"sha256_password,omitempty"` - TimeTick uint64 `protobuf:"varint,6,opt,name=time_tick,json=timeTick,proto3" json:"time_tick,omitempty"` // the timetick in wal which the credential updates + Sha256Password string `protobuf:"bytes,5,opt,name=sha256_password,json=sha256Password,proto3" json:"sha256_password,omitempty"` // not save in metadata. + TimeTick uint64 `protobuf:"varint,6,opt,name=time_tick,json=timeTick,proto3" json:"time_tick,omitempty"` // the timetick in wal which the credential updates } func (x *CredentialInfo) Reset() { @@ -2526,6 +2528,7 @@ func (x *CredentialInfo) GetEncryptedPassword() string { return "" } +// Deprecated: Marked as deprecated in internal.proto. func (x *CredentialInfo) GetTenant() string { if x != nil { return x.Tenant @@ -2533,6 +2536,7 @@ func (x *CredentialInfo) GetTenant() string { return "" } +// Deprecated: Marked as deprecated in internal.proto. func (x *CredentialInfo) GetIsSuper() bool { if x != nil { return x.IsSuper @@ -4485,283 +4489,284 @@ var file_internal_proto_rawDesc = []byte{ 0x28, 0x04, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x64, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xd4, 0x01, 0x0a, 0x0e, + 0x6c, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xdc, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, - 0x64, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, - 0x74, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x53, 0x75, 0x70, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, - 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x50, 0x61, 0x73, - 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x69, - 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x54, 0x69, - 0x63, 0x6b, 0x22, 0x45, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73, 0x67, 0x42, - 0x61, 0x73, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, 0xdf, 0x01, 0x0a, 0x12, 0x4c, 0x69, - 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, - 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, - 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x52, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x76, 0x69, - 0x6c, 0x65, 0x67, 0x65, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x27, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, - 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x76, - 0x69, 0x6c, 0x65, 0x67, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x22, 0x67, 0x0a, 0x19, 0x53, - 0x68, 0x6f, 0x77, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73, 0x67, - 0x42, 0x61, 0x73, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, - 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, - 0x74, 0x65, 0x72, 0x6e, 0x22, 0x9a, 0x01, 0x0a, 0x1a, 0x53, 0x68, 0x6f, 0x77, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x47, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x50, 0x61, - 0x69, 0x72, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x22, 0x45, 0x0a, 0x04, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2f, 0x0a, 0x02, 0x72, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x52, 0x61, - 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x02, 0x72, 0x74, 0x12, 0x0c, 0x0a, 0x01, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x01, 0x72, 0x22, 0x32, 0x0a, 0x0a, 0x49, 0x6d, 0x70, 0x6f, - 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x22, 0xb7, 0x03, 0x0a, - 0x15, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x04, 0x64, 0x62, 0x49, 0x44, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x04, 0x64, 0x62, 0x49, 0x44, 0x12, 0x22, - 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x49, 0x44, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, - 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x03, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, - 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x06, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x12, 0x37, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x07, - 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x64, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1a, 0x0a, 0x06, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x74, + 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x65, + 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x69, 0x73, 0x53, + 0x75, 0x70, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x5f, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, + 0x68, 0x61, 0x32, 0x35, 0x36, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x54, 0x69, 0x63, 0x6b, 0x22, 0x45, 0x0a, 0x11, 0x4c, 0x69, + 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, - 0x6d, 0x6f, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x50, 0x61, 0x69, 0x72, - 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x61, 0x74, - 0x61, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x0d, 0x64, 0x61, 0x74, 0x61, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x22, 0xee, 0x01, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61, - 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x37, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x46, - 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, - 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x07, - 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x5b, 0x0a, 0x0e, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, - 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, - 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, - 0x6f, 0x62, 0x49, 0x44, 0x22, 0x49, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, - 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x22, - 0x81, 0x02, 0x0a, 0x12, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, - 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x65, 0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x23, 0x0a, 0x0d, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x77, 0x73, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x52, 0x6f, 0x77, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x72, 0x6f, - 0x77, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x52, - 0x6f, 0x77, 0x73, 0x22, 0xc6, 0x03, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, - 0x70, 0x6f, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, - 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, - 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, - 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x52, 0x0a, 0x0f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x70, 0x72, - 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, - 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, - 0x6b, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0e, 0x74, 0x61, 0x73, 0x6b, 0x50, - 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x1d, - 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x1d, 0x0a, - 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x54, 0x0a, 0x1a, - 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x62, - 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x64, 0x62, 0x49, 0x44, 0x12, 0x22, - 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x49, 0x44, 0x22, 0x56, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x86, 0x02, 0x0a, 0x13, 0x4c, - 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x49, 0x44, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x73, 0x12, - 0x3d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, - 0x25, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4a, 0x6f, - 0x62, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x12, 0x18, - 0x0a, 0x07, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x07, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x70, 0x72, - 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, - 0x6d, 0x65, 0x73, 0x22, 0x74, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, - 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, - 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x67, - 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x73, - 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x73, 0x22, 0x3f, 0x0a, 0x0b, 0x46, 0x69, 0x65, - 0x6c, 0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, - 0x64, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, - 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x67, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x03, 0x52, 0x06, 0x6c, 0x6f, 0x67, 0x49, 0x44, 0x73, 0x22, 0x82, 0x04, 0x0a, 0x0b, 0x53, - 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, - 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, - 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x20, 0x0a, 0x0b, - 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1a, - 0x0a, 0x08, 0x76, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x76, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x75, - 0x6d, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6e, 0x75, - 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x37, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, - 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x37, - 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, - 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, - 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, - 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x73, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x53, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x5f, 0x6c, - 0x6f, 0x67, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76, - 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x52, 0x0a, 0x69, - 0x6e, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x41, 0x0a, 0x0a, 0x64, 0x65, 0x6c, - 0x74, 0x61, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, - 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, - 0x67, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x41, 0x0a, 0x0a, - 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x69, - 0x6e, 0x6c, 0x6f, 0x67, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x73, 0x4c, 0x6f, 0x67, 0x73, 0x22, - 0x96, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, + 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73, 0x67, 0x42, 0x61, 0x73, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, + 0x65, 0x22, 0xdf, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, + 0x0c, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x73, + 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, + 0x52, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x5f, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6d, 0x69, 0x6c, 0x76, + 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, + 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x73, 0x22, 0x67, 0x0a, 0x19, 0x53, 0x68, 0x6f, 0x77, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73, 0x67, 0x42, 0x61, 0x73, 0x65, 0x52, 0x04, 0x62, 0x61, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x22, 0x9a, 0x01, 0x0a, + 0x1a, 0x53, 0x68, 0x6f, 0x77, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x46, 0x0a, 0x0c, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, - 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0c, 0x73, 0x65, 0x67, 0x6d, - 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x73, 0x22, 0x4a, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x51, - 0x75, 0x6f, 0x74, 0x61, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x73, 0x67, 0x42, 0x61, 0x73, 0x65, 0x52, 0x04, - 0x62, 0x61, 0x73, 0x65, 0x22, 0x71, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, - 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x2a, 0x45, 0x0a, 0x09, 0x52, 0x61, 0x74, 0x65, 0x53, - 0x63, 0x6f, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x10, - 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x10, 0x01, 0x12, - 0x0e, 0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x02, 0x12, - 0x0d, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x03, 0x2a, 0xc4, - 0x01, 0x0a, 0x08, 0x52, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x44, - 0x44, 0x4c, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x00, 0x12, 0x10, - 0x0a, 0x0c, 0x44, 0x44, 0x4c, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x01, - 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x44, 0x4c, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x10, 0x02, 0x12, 0x0c, - 0x0a, 0x08, 0x44, 0x44, 0x4c, 0x46, 0x6c, 0x75, 0x73, 0x68, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, - 0x44, 0x44, 0x4c, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x04, 0x12, - 0x0d, 0x0a, 0x09, 0x44, 0x4d, 0x4c, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x10, 0x05, 0x12, 0x0d, - 0x0a, 0x09, 0x44, 0x4d, 0x4c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x10, 0x06, 0x12, 0x0f, 0x0a, - 0x0b, 0x44, 0x4d, 0x4c, 0x42, 0x75, 0x6c, 0x6b, 0x4c, 0x6f, 0x61, 0x64, 0x10, 0x07, 0x12, 0x0d, - 0x0a, 0x09, 0x44, 0x51, 0x4c, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x10, 0x08, 0x12, 0x0c, 0x0a, - 0x08, 0x44, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x10, 0x09, 0x12, 0x0d, 0x0a, 0x09, 0x44, - 0x4d, 0x4c, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x10, 0x0a, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x44, - 0x4c, 0x44, 0x42, 0x10, 0x0b, 0x2a, 0x83, 0x01, 0x0a, 0x0e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, - 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, - 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x01, 0x12, - 0x10, 0x0a, 0x0c, 0x50, 0x72, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x10, - 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x10, 0x03, - 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, - 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x06, 0x12, 0x0b, - 0x0a, 0x07, 0x53, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x10, 0x07, 0x42, 0x35, 0x5a, 0x33, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, - 0x2d, 0x69, 0x6f, 0x2f, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x76, - 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x47, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4b, 0x65, + 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x45, 0x0a, 0x04, 0x52, 0x61, 0x74, + 0x65, 0x12, 0x2f, 0x0a, 0x02, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, + 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x02, + 0x72, 0x74, 0x12, 0x0c, 0x0a, 0x01, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x01, 0x72, + 0x22, 0x32, 0x0a, 0x0a, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, + 0x61, 0x74, 0x68, 0x73, 0x22, 0xb7, 0x03, 0x0a, 0x15, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x16, + 0x0a, 0x04, 0x64, 0x62, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x04, 0x64, 0x62, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, + 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, + 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x49, 0x44, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x61, 0x72, 0x74, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x06, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6d, + 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x37, 0x0a, 0x05, 0x66, + 0x69, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, + 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, + 0x69, 0x6c, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x64, 0x61, 0x74, 0x61, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, + 0x44, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x22, 0xee, + 0x01, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x74, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, + 0x5b, 0x0a, 0x0e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x22, 0x49, 0x0a, 0x18, + 0x47, 0x65, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x22, 0x81, 0x02, 0x0a, 0x12, 0x49, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, + 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, + 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, + 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6d, 0x70, 0x6f, 0x72, + 0x74, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, + 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x1d, 0x0a, 0x0a, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x52, 0x6f, 0x77, 0x73, 0x22, 0xc6, 0x03, 0x0a, 0x19, + 0x47, 0x65, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, + 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3b, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, + 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x52, 0x0a, + 0x0f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, + 0x73, 0x52, 0x0e, 0x74, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x6f, + 0x77, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, + 0x65, 0x64, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, + 0x72, 0x6f, 0x77, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x54, 0x69, 0x6d, 0x65, 0x22, 0x54, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x62, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x04, 0x64, 0x62, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, + 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x56, 0x0a, 0x12, 0x4c, 0x69, + 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x17, 0x0a, 0x07, 0x64, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, + 0x6d, 0x65, 0x22, 0x86, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, + 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x06, 0x6a, 0x6f, 0x62, 0x49, 0x44, 0x73, 0x12, 0x3d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x73, + 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x74, 0x0a, 0x16, 0x47, + 0x65, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, + 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, + 0x73, 0x22, 0x3f, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, 0x67, + 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x07, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, + 0x67, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x06, 0x6c, 0x6f, 0x67, 0x49, + 0x44, 0x73, 0x22, 0x82, 0x04, 0x0a, 0x0b, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, + 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x76, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x75, 0x6d, 0x5f, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6e, 0x75, 0x6d, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x37, 0x0a, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, + 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, + 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x73, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x53, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0b, + 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, + 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x6f, 0x67, + 0x73, 0x12, 0x41, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, + 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x74, 0x61, + 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x41, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x6c, 0x6f, + 0x67, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x52, 0x09, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x4c, 0x6f, 0x67, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, + 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x0c, 0x73, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, + 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x0c, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x73, + 0x22, 0x4a, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x04, 0x62, 0x61, + 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, + 0x73, 0x67, 0x42, 0x61, 0x73, 0x65, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x22, 0x71, 0x0a, 0x17, + 0x47, 0x65, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, + 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x2a, + 0x45, 0x0a, 0x09, 0x52, 0x61, 0x74, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x61, 0x74, + 0x61, 0x62, 0x61, 0x73, 0x65, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x03, 0x2a, 0xc4, 0x01, 0x0a, 0x08, 0x52, 0x61, 0x74, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x44, 0x44, 0x4c, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x44, 0x4c, 0x50, 0x61, 0x72, + 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x44, 0x4c, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x44, 0x4c, 0x46, 0x6c, 0x75, + 0x73, 0x68, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x44, 0x44, 0x4c, 0x43, 0x6f, 0x6d, 0x70, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x4d, 0x4c, 0x49, 0x6e, + 0x73, 0x65, 0x72, 0x74, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x4d, 0x4c, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x10, 0x06, 0x12, 0x0f, 0x0a, 0x0b, 0x44, 0x4d, 0x4c, 0x42, 0x75, 0x6c, 0x6b, + 0x4c, 0x6f, 0x61, 0x64, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x51, 0x4c, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x10, 0x08, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x51, 0x4c, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x10, 0x09, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x4d, 0x4c, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, + 0x10, 0x0a, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x44, 0x4c, 0x44, 0x42, 0x10, 0x0b, 0x2a, 0x83, 0x01, + 0x0a, 0x0e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x72, 0x65, 0x49, 0x6d, + 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x6d, 0x70, + 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c, + 0x65, 0x64, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x75, 0x69, 0x6c, + 0x64, 0x69, 0x6e, 0x67, 0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x6f, 0x72, 0x74, 0x69, 0x6e, + 0x67, 0x10, 0x07, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x6d, 0x69, 0x6c, 0x76, 0x75, 0x73, 0x2d, 0x69, 0x6f, 0x2f, 0x6d, 0x69, 0x6c, 0x76, + 0x75, 0x73, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var (