enhance: Enable properites in database meta (#31394)

issue: #30040

This PR add properties in database meta, so we can store some database
level info.

Signed-off-by: Wei Liu <wei.liu@zilliz.com>
This commit is contained in:
wei liu 2024-03-19 19:21:06 +08:00 committed by GitHub
parent 68af832405
commit e6d50def4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 211 additions and 3 deletions

View File

@ -16,6 +16,7 @@ type RootCoordCatalog interface {
CreateDatabase(ctx context.Context, db *model.Database, ts typeutil.Timestamp) error
DropDatabase(ctx context.Context, dbID int64, ts typeutil.Timestamp) error
ListDatabases(ctx context.Context, ts typeutil.Timestamp) ([]*model.Database, error)
AlterDatabase(ctx context.Context, newDB *model.Database, ts typeutil.Timestamp) error
CreateCollection(ctx context.Context, collectionInfo *model.Collection, ts typeutil.Timestamp) error
GetCollectionByID(ctx context.Context, dbID int64, ts typeutil.Timestamp, collectionID typeutil.UniqueID) (*model.Collection, error)

View File

@ -110,6 +110,16 @@ func (kc *Catalog) CreateDatabase(ctx context.Context, db *model.Database, ts ty
return kc.Snapshot.Save(key, string(v), ts)
}
func (kc *Catalog) AlterDatabase(ctx context.Context, newColl *model.Database, ts typeutil.Timestamp) error {
key := BuildDatabaseKey(newColl.ID)
dbInfo := model.MarshalDatabaseModel(newColl)
v, err := proto.Marshal(dbInfo)
if err != nil {
return err
}
return kc.Snapshot.Save(key, string(v), ts)
}
func (kc *Catalog) DropDatabase(ctx context.Context, dbID int64, ts typeutil.Timestamp) error {
key := BuildDatabaseKey(dbID)
return kc.Snapshot.MultiSaveAndRemoveWithPrefix(nil, []string{key}, ts)

View File

@ -2524,3 +2524,30 @@ func TestRBAC_Grant(t *testing.T) {
}
})
}
func TestCatalog_AlterDatabase(t *testing.T) {
kvmock := mocks.NewSnapShotKV(t)
c := &Catalog{Snapshot: kvmock}
db := model.NewDatabase(1, "db", pb.DatabaseState_DatabaseCreated)
kvmock.EXPECT().Save(mock.Anything, mock.Anything, mock.Anything).Return(nil)
ctx := context.Background()
// test alter database success
newDB := db.Clone()
db.Properties = []*commonpb.KeyValuePair{
{
Key: "key1",
Value: "value1",
},
}
err := c.AlterDatabase(ctx, newDB, typeutil.ZeroTimestamp)
assert.NoError(t, err)
// test alter database fail
mockErr := errors.New("access kv store error")
kvmock.ExpectedCalls = nil
kvmock.EXPECT().Save(mock.Anything, mock.Anything, mock.Anything).Return(mockErr)
err = c.AlterDatabase(ctx, newDB, typeutil.ZeroTimestamp)
assert.ErrorIs(t, err, mockErr)
}

View File

@ -159,6 +159,50 @@ func (_c *RootCoordCatalog_AlterCredential_Call) RunAndReturn(run func(context.C
return _c
}
// AlterDatabase provides a mock function with given fields: ctx, newDB, ts
func (_m *RootCoordCatalog) AlterDatabase(ctx context.Context, newDB *model.Database, ts uint64) error {
ret := _m.Called(ctx, newDB, ts)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *model.Database, uint64) error); ok {
r0 = rf(ctx, newDB, ts)
} else {
r0 = ret.Error(0)
}
return r0
}
// RootCoordCatalog_AlterDatabase_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AlterDatabase'
type RootCoordCatalog_AlterDatabase_Call struct {
*mock.Call
}
// AlterDatabase is a helper method to define mock.On call
// - ctx context.Context
// - newDB *model.Database
// - ts uint64
func (_e *RootCoordCatalog_Expecter) AlterDatabase(ctx interface{}, newDB interface{}, ts interface{}) *RootCoordCatalog_AlterDatabase_Call {
return &RootCoordCatalog_AlterDatabase_Call{Call: _e.mock.On("AlterDatabase", ctx, newDB, ts)}
}
func (_c *RootCoordCatalog_AlterDatabase_Call) Run(run func(ctx context.Context, newDB *model.Database, ts uint64)) *RootCoordCatalog_AlterDatabase_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*model.Database), args[2].(uint64))
})
return _c
}
func (_c *RootCoordCatalog_AlterDatabase_Call) Return(_a0 error) *RootCoordCatalog_AlterDatabase_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *RootCoordCatalog_AlterDatabase_Call) RunAndReturn(run func(context.Context, *model.Database, uint64) error) *RootCoordCatalog_AlterDatabase_Call {
_c.Call.Return(run)
return _c
}
// AlterGrant provides a mock function with given fields: ctx, tenant, entity, operateType
func (_m *RootCoordCatalog) AlterGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error {
ret := _m.Called(ctx, tenant, entity, operateType)

View File

@ -3,7 +3,9 @@ package model
import (
"time"
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb"
pb "github.com/milvus-io/milvus/internal/proto/etcdpb"
"github.com/milvus-io/milvus/pkg/common"
"github.com/milvus-io/milvus/pkg/util"
)
@ -13,14 +15,16 @@ type Database struct {
Name string
State pb.DatabaseState
CreatedTime uint64
Properties []*commonpb.KeyValuePair
}
func NewDatabase(id int64, name string, sate pb.DatabaseState) *Database {
func NewDatabase(id int64, name string, state pb.DatabaseState) *Database {
return &Database{
ID: id,
Name: name,
State: sate,
State: state,
CreatedTime: uint64(time.Now().UnixNano()),
Properties: make([]*commonpb.KeyValuePair, 0),
}
}
@ -39,6 +43,7 @@ func (c *Database) Clone() *Database {
Name: c.Name,
State: c.State,
CreatedTime: c.CreatedTime,
Properties: common.CloneKeyValuePairs(c.Properties),
}
}
@ -47,7 +52,8 @@ func (c *Database) Equal(other Database) bool {
c.Name == other.Name &&
c.ID == other.ID &&
c.State == other.State &&
c.CreatedTime == other.CreatedTime
c.CreatedTime == other.CreatedTime &&
checkParamsEqual(c.Properties, other.Properties)
}
func MarshalDatabaseModel(db *Database) *pb.DatabaseInfo {
@ -61,6 +67,7 @@ func MarshalDatabaseModel(db *Database) *pb.DatabaseInfo {
Name: db.Name,
State: db.State,
CreatedTime: db.CreatedTime,
Properties: db.Properties,
}
}
@ -75,5 +82,6 @@ func UnmarshalDatabaseModel(info *pb.DatabaseInfo) *Database {
CreatedTime: info.GetCreatedTime(),
State: info.GetState(),
TenantID: info.GetTenantId(),
Properties: info.GetProperties(),
}
}

View File

@ -5,16 +5,28 @@ import (
"github.com/stretchr/testify/assert"
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb"
"github.com/milvus-io/milvus/internal/proto/etcdpb"
)
var (
properties = []*commonpb.KeyValuePair{
{
Key: "key1",
Value: "value1",
},
{
Key: "key2",
Value: "value2",
},
}
dbPB = &etcdpb.DatabaseInfo{
TenantId: "1",
Name: "test",
Id: 1,
CreatedTime: 1,
State: etcdpb.DatabaseState_DatabaseCreated,
Properties: properties,
}
dbModel = &Database{
@ -23,6 +35,7 @@ var (
ID: 1,
CreatedTime: 1,
State: etcdpb.DatabaseState_DatabaseCreated,
Properties: properties,
}
)
@ -41,6 +54,7 @@ func TestUnmarshalDatabaseModel(t *testing.T) {
func TestDatabaseCloneAndEqual(t *testing.T) {
clone := dbModel.Clone()
assert.Equal(t, dbModel, clone)
assert.True(t, dbModel.Equal(*clone))
}
func TestDatabaseAvailable(t *testing.T) {

View File

@ -93,6 +93,7 @@ message DatabaseInfo {
int64 id = 3;
DatabaseState state = 4;
uint64 created_time = 5;
repeated common.KeyValuePair properties = 6;
}
message SegmentIndexInfo {

View File

@ -276,6 +276,23 @@ func (mt *MetaTable) createDatabasePrivate(ctx context.Context, db *model.Databa
return nil
}
func (mt *MetaTable) AlterDatabase(ctx context.Context, oldDB *model.Database, newDB *model.Database, ts typeutil.Timestamp) error {
mt.ddLock.Lock()
defer mt.ddLock.Unlock()
if oldDB.Name != newDB.Name || oldDB.ID != newDB.ID || oldDB.State != newDB.State {
return fmt.Errorf("alter database name/id is not supported!")
}
ctx1 := contextutil.WithTenantID(ctx, Params.CommonCfg.ClusterName.GetValue())
if err := mt.catalog.AlterDatabase(ctx1, newDB, ts); err != nil {
return err
}
mt.dbName2Meta[oldDB.Name] = newDB
log.Info("alter database finished", zap.String("dbName", oldDB.Name), zap.Uint64("ts", ts))
return nil
}
func (mt *MetaTable) DropDatabase(ctx context.Context, dbName string, ts typeutil.Timestamp) error {
mt.ddLock.Lock()
defer mt.ddLock.Unlock()

View File

@ -26,6 +26,7 @@ import (
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"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"
"github.com/milvus-io/milvus/internal/metastore/kv/rootcoord"
@ -1722,6 +1723,91 @@ func TestMetaTable_CreateDatabase(t *testing.T) {
})
}
func TestAlterDatabase(t *testing.T) {
t.Run("normal case", func(t *testing.T) {
catalog := mocks.NewRootCoordCatalog(t)
catalog.On("AlterDatabase",
mock.Anything,
mock.Anything,
mock.Anything,
).Return(nil)
db := model.NewDatabase(1, "db1", pb.DatabaseState_DatabaseCreated)
meta := &MetaTable{
dbName2Meta: map[string]*model.Database{
"db1": db,
},
names: newNameDb(),
aliases: newNameDb(),
catalog: catalog,
}
newDB := db.Clone()
db.Properties = []*commonpb.KeyValuePair{
{
Key: "key1",
Value: "value1",
},
}
err := meta.AlterDatabase(context.TODO(), db, newDB, typeutil.ZeroTimestamp)
assert.NoError(t, err)
})
t.Run("access catalog failed", func(t *testing.T) {
catalog := mocks.NewRootCoordCatalog(t)
mockErr := errors.New("access catalog failed")
catalog.On("AlterDatabase",
mock.Anything,
mock.Anything,
mock.Anything,
).Return(mockErr)
db := model.NewDatabase(1, "db1", pb.DatabaseState_DatabaseCreated)
meta := &MetaTable{
dbName2Meta: map[string]*model.Database{
"db1": db,
},
names: newNameDb(),
aliases: newNameDb(),
catalog: catalog,
}
newDB := db.Clone()
db.Properties = []*commonpb.KeyValuePair{
{
Key: "key1",
Value: "value1",
},
}
err := meta.AlterDatabase(context.TODO(), db, newDB, typeutil.ZeroTimestamp)
assert.ErrorIs(t, err, mockErr)
})
t.Run("alter database name", func(t *testing.T) {
catalog := mocks.NewRootCoordCatalog(t)
db := model.NewDatabase(1, "db1", pb.DatabaseState_DatabaseCreated)
meta := &MetaTable{
dbName2Meta: map[string]*model.Database{
"db1": db,
},
names: newNameDb(),
aliases: newNameDb(),
catalog: catalog,
}
newDB := db.Clone()
newDB.Name = "db2"
db.Properties = []*commonpb.KeyValuePair{
{
Key: "key1",
Value: "value1",
},
}
err := meta.AlterDatabase(context.TODO(), db, newDB, typeutil.ZeroTimestamp)
assert.Error(t, err)
})
}
func TestMetaTable_EmtpyDatabaseName(t *testing.T) {
t.Run("getDatabaseByNameInternal with empty db", func(t *testing.T) {
mt := &MetaTable{