mirror of https://github.com/milvus-io/milvus.git
950 lines
30 KiB
Go
950 lines
30 KiB
Go
package kv
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"path"
|
|
"reflect"
|
|
"strconv"
|
|
|
|
"github.com/milvus-io/milvus/internal/util/funcutil"
|
|
|
|
"github.com/milvus-io/milvus/internal/proto/milvuspb"
|
|
"github.com/milvus-io/milvus/internal/util"
|
|
|
|
"github.com/milvus-io/milvus/internal/metastore"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/milvus-io/milvus/internal/kv"
|
|
"github.com/milvus-io/milvus/internal/log"
|
|
"github.com/milvus-io/milvus/internal/metastore/model"
|
|
pb "github.com/milvus-io/milvus/internal/proto/etcdpb"
|
|
"github.com/milvus-io/milvus/internal/proto/internalpb"
|
|
"github.com/milvus-io/milvus/internal/proto/schemapb"
|
|
"github.com/milvus-io/milvus/internal/util/typeutil"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type Catalog struct {
|
|
Txn kv.TxnKV
|
|
Snapshot kv.SnapShotKV
|
|
}
|
|
|
|
func (kc *Catalog) CreateCollection(ctx context.Context, coll *model.Collection, ts typeutil.Timestamp) error {
|
|
k1 := fmt.Sprintf("%s/%d", CollectionMetaPrefix, coll.CollectionID)
|
|
collInfo := model.MarshalCollectionModel(coll)
|
|
v1, err := proto.Marshal(collInfo)
|
|
if err != nil {
|
|
log.Error("create collection marshal fail", zap.String("key", k1), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
// save ddOpStr into etcd
|
|
kvs := map[string]string{k1: string(v1)}
|
|
for k, v := range coll.Extra {
|
|
kvs[k] = v
|
|
}
|
|
|
|
err = kc.Snapshot.MultiSave(kvs, ts)
|
|
if err != nil {
|
|
log.Error("create collection persist meta fail", zap.String("key", k1), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) CreatePartition(ctx context.Context, coll *model.Collection, ts typeutil.Timestamp) error {
|
|
k1 := fmt.Sprintf("%s/%d", CollectionMetaPrefix, coll.CollectionID)
|
|
collInfo := model.MarshalCollectionModel(coll)
|
|
v1, err := proto.Marshal(collInfo)
|
|
if err != nil {
|
|
log.Error("create partition marshal fail", zap.String("key", k1), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
kvs := map[string]string{k1: string(v1)}
|
|
err = kc.Snapshot.MultiSave(kvs, ts)
|
|
if err != nil {
|
|
log.Error("create partition persist meta fail", zap.String("key", k1), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
// save ddOpStr into etcd
|
|
err = kc.Txn.MultiSave(coll.Extra)
|
|
if err != nil {
|
|
// will not panic, missing create msg
|
|
log.Warn("create partition persist ddop meta fail", zap.Int64("collectionID", coll.CollectionID), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) CreateIndex(ctx context.Context, col *model.Collection, index *model.Index) error {
|
|
k1 := path.Join(CollectionMetaPrefix, strconv.FormatInt(col.CollectionID, 10))
|
|
v1, err := proto.Marshal(model.MarshalCollectionModel(col))
|
|
if err != nil {
|
|
log.Error("create index marshal fail", zap.String("key", k1), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
k2 := path.Join(IndexMetaPrefix, strconv.FormatInt(index.IndexID, 10))
|
|
v2, err := proto.Marshal(model.MarshalIndexModel(index))
|
|
if err != nil {
|
|
log.Error("create index marshal fail", zap.String("key", k2), zap.Error(err))
|
|
return err
|
|
}
|
|
meta := map[string]string{k1: string(v1), k2: string(v2)}
|
|
|
|
err = kc.Txn.MultiSave(meta)
|
|
if err != nil {
|
|
log.Error("create index persist meta fail", zap.String("key", k1), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) alterAddIndex(ctx context.Context, oldIndex *model.Index, newIndex *model.Index) error {
|
|
kvs := make(map[string]string, len(newIndex.SegmentIndexes))
|
|
for segID, newSegIdx := range newIndex.SegmentIndexes {
|
|
oldSegIdx, ok := oldIndex.SegmentIndexes[segID]
|
|
if !ok || !reflect.DeepEqual(oldSegIdx, newSegIdx) {
|
|
segment := newSegIdx.Segment
|
|
k := fmt.Sprintf("%s/%d/%d/%d/%d", SegmentIndexMetaPrefix, newIndex.CollectionID, newIndex.IndexID, segment.PartitionID, segment.SegmentID)
|
|
segIdxInfo := &pb.SegmentIndexInfo{
|
|
CollectionID: newIndex.CollectionID,
|
|
PartitionID: segment.PartitionID,
|
|
SegmentID: segment.SegmentID,
|
|
BuildID: newSegIdx.BuildID,
|
|
EnableIndex: newSegIdx.EnableIndex,
|
|
CreateTime: newSegIdx.CreateTime,
|
|
FieldID: newIndex.FieldID,
|
|
IndexID: newIndex.IndexID,
|
|
}
|
|
|
|
v, err := proto.Marshal(segIdxInfo)
|
|
if err != nil {
|
|
log.Error("alter index marshal fail", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
kvs[k] = string(v)
|
|
}
|
|
}
|
|
|
|
if oldIndex.CreateTime != newIndex.CreateTime || oldIndex.IsDeleted != newIndex.IsDeleted {
|
|
idxPb := model.MarshalIndexModel(newIndex)
|
|
k := fmt.Sprintf("%s/%d/%d", IndexMetaPrefix, newIndex.CollectionID, newIndex.IndexID)
|
|
v, err := proto.Marshal(idxPb)
|
|
if err != nil {
|
|
log.Error("alter index marshal fail", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
kvs[k] = string(v)
|
|
}
|
|
|
|
if len(kvs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
err := kc.Txn.MultiSave(kvs)
|
|
if err != nil {
|
|
log.Error("alter add index persist meta fail", zap.Any("segmentIndex", newIndex.SegmentIndexes), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) alterDeleteIndex(ctx context.Context, oldIndex *model.Index, newIndex *model.Index) error {
|
|
delKeys := make([]string, len(newIndex.SegmentIndexes))
|
|
for _, segIdx := range newIndex.SegmentIndexes {
|
|
delKeys = append(delKeys, fmt.Sprintf("%s/%d/%d/%d/%d",
|
|
SegmentIndexMetaPrefix, newIndex.CollectionID, newIndex.IndexID, segIdx.PartitionID, segIdx.SegmentID))
|
|
}
|
|
|
|
if len(delKeys) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if err := kc.Txn.MultiRemove(delKeys); err != nil {
|
|
log.Error("alter delete index persist meta fail", zap.Any("keys", delKeys), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) AlterIndex(ctx context.Context, oldIndex *model.Index, newIndex *model.Index, alterType metastore.AlterType) error {
|
|
switch alterType {
|
|
case metastore.ADD:
|
|
return kc.alterAddIndex(ctx, oldIndex, newIndex)
|
|
case metastore.DELETE:
|
|
return kc.alterDeleteIndex(ctx, oldIndex, newIndex)
|
|
default:
|
|
return errors.New("Unknown alter type:" + fmt.Sprintf("%d", alterType))
|
|
}
|
|
}
|
|
|
|
func (kc *Catalog) CreateAlias(ctx context.Context, collection *model.Collection, ts typeutil.Timestamp) error {
|
|
k := fmt.Sprintf("%s/%s", CollectionAliasMetaPrefix, collection.Aliases[0])
|
|
v, err := proto.Marshal(&pb.CollectionInfo{ID: collection.CollectionID, Schema: &schemapb.CollectionSchema{Name: collection.Aliases[0]}})
|
|
if err != nil {
|
|
log.Error("create alias marshal fail", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
err = kc.Snapshot.Save(k, string(v), ts)
|
|
if err != nil {
|
|
log.Error("create alias persist meta fail", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) CreateCredential(ctx context.Context, credential *model.Credential) error {
|
|
k := fmt.Sprintf("%s/%s", CredentialPrefix, credential.Username)
|
|
v, err := json.Marshal(&internalpb.CredentialInfo{EncryptedPassword: credential.EncryptedPassword})
|
|
if err != nil {
|
|
log.Error("create credential marshal fail", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
err = kc.Txn.Save(k, string(v))
|
|
if err != nil {
|
|
log.Error("create credential persist meta fail", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) GetCollectionByID(ctx context.Context, collectionID typeutil.UniqueID, ts typeutil.Timestamp) (*model.Collection, error) {
|
|
collKey := fmt.Sprintf("%s/%d", CollectionMetaPrefix, collectionID)
|
|
collVal, err := kc.Snapshot.Load(collKey, ts)
|
|
if err != nil {
|
|
log.Error("get collection meta fail", zap.String("key", collKey), zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
collMeta := &pb.CollectionInfo{}
|
|
err = proto.Unmarshal([]byte(collVal), collMeta)
|
|
if err != nil {
|
|
log.Error("collection meta marshal fail", zap.String("key", collKey), zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
return model.UnmarshalCollectionModel(collMeta), nil
|
|
}
|
|
|
|
func (kc *Catalog) CollectionExists(ctx context.Context, collectionID typeutil.UniqueID, ts typeutil.Timestamp) bool {
|
|
_, err := kc.GetCollectionByID(ctx, collectionID, ts)
|
|
return err == nil
|
|
}
|
|
|
|
func (kc *Catalog) GetCredential(ctx context.Context, username string) (*model.Credential, error) {
|
|
k := fmt.Sprintf("%s/%s", CredentialPrefix, username)
|
|
v, err := kc.Txn.Load(k)
|
|
if err != nil {
|
|
log.Warn("get credential meta fail", zap.String("key", k), zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
credentialInfo := internalpb.CredentialInfo{}
|
|
err = json.Unmarshal([]byte(v), &credentialInfo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unmarshal credential info err:%w", err)
|
|
}
|
|
|
|
return &model.Credential{Username: username, EncryptedPassword: credentialInfo.EncryptedPassword}, nil
|
|
}
|
|
|
|
func (kc *Catalog) AlterAlias(ctx context.Context, collection *model.Collection, ts typeutil.Timestamp) error {
|
|
return kc.CreateAlias(ctx, collection, ts)
|
|
}
|
|
|
|
func (kc *Catalog) DropCollection(ctx context.Context, collectionInfo *model.Collection, ts typeutil.Timestamp) error {
|
|
delMetakeysSnap := []string{
|
|
fmt.Sprintf("%s/%d", CollectionMetaPrefix, collectionInfo.CollectionID),
|
|
}
|
|
for _, alias := range collectionInfo.Aliases {
|
|
delMetakeysSnap = append(delMetakeysSnap,
|
|
fmt.Sprintf("%s/%s", CollectionAliasMetaPrefix, alias),
|
|
)
|
|
}
|
|
|
|
err := kc.Snapshot.MultiSaveAndRemoveWithPrefix(map[string]string{}, delMetakeysSnap, ts)
|
|
if err != nil {
|
|
log.Error("drop collection update meta fail", zap.Int64("collectionID", collectionInfo.CollectionID), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
// Txn operation
|
|
kvs := map[string]string{}
|
|
for k, v := range collectionInfo.Extra {
|
|
kvs[k] = v
|
|
}
|
|
|
|
delMetaKeysTxn := []string{
|
|
fmt.Sprintf("%s/%d", SegmentIndexMetaPrefix, collectionInfo.CollectionID),
|
|
fmt.Sprintf("%s/%d", IndexMetaPrefix, collectionInfo.CollectionID),
|
|
}
|
|
|
|
err = kc.Txn.MultiSaveAndRemoveWithPrefix(kvs, delMetaKeysTxn)
|
|
if err != nil {
|
|
log.Warn("drop collection update meta fail", zap.Int64("collectionID", collectionInfo.CollectionID), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) DropPartition(ctx context.Context, collectionInfo *model.Collection, partitionID typeutil.UniqueID, ts typeutil.Timestamp) error {
|
|
collMeta := model.MarshalCollectionModel(collectionInfo)
|
|
k := path.Join(CollectionMetaPrefix, strconv.FormatInt(collectionInfo.CollectionID, 10))
|
|
v, err := proto.Marshal(collMeta)
|
|
if err != nil {
|
|
log.Error("drop partition marshal fail", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
err = kc.Snapshot.Save(k, string(v), ts)
|
|
if err != nil {
|
|
log.Error("drop partition update collection meta fail",
|
|
zap.Int64("collectionID", collectionInfo.CollectionID),
|
|
zap.Int64("partitionID", partitionID),
|
|
zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
var delMetaKeys []string
|
|
for _, idxInfo := range collMeta.FieldIndexes {
|
|
k := fmt.Sprintf("%s/%d/%d/%d", SegmentIndexMetaPrefix, collMeta.ID, idxInfo.IndexID, partitionID)
|
|
delMetaKeys = append(delMetaKeys, k)
|
|
}
|
|
|
|
// Txn operation
|
|
metaTxn := map[string]string{}
|
|
for k, v := range collectionInfo.Extra {
|
|
metaTxn[k] = v
|
|
}
|
|
err = kc.Txn.MultiSaveAndRemoveWithPrefix(metaTxn, delMetaKeys)
|
|
if err != nil {
|
|
log.Warn("drop partition update meta fail",
|
|
zap.Int64("collectionID", collectionInfo.CollectionID),
|
|
zap.Int64("partitionID", partitionID),
|
|
zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) DropIndex(ctx context.Context, collectionInfo *model.Collection, dropIdxID typeutil.UniqueID, ts typeutil.Timestamp) error {
|
|
collMeta := model.MarshalCollectionModel(collectionInfo)
|
|
k := path.Join(CollectionMetaPrefix, strconv.FormatInt(collectionInfo.CollectionID, 10))
|
|
v, err := proto.Marshal(collMeta)
|
|
if err != nil {
|
|
log.Error("drop index marshal fail", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
saveMeta := map[string]string{k: string(v)}
|
|
|
|
delMeta := []string{
|
|
fmt.Sprintf("%s/%d/%d", SegmentIndexMetaPrefix, collectionInfo.CollectionID, dropIdxID),
|
|
fmt.Sprintf("%s/%d/%d", IndexMetaPrefix, collectionInfo.CollectionID, dropIdxID),
|
|
}
|
|
|
|
err = kc.Txn.MultiSaveAndRemoveWithPrefix(saveMeta, delMeta)
|
|
if err != nil {
|
|
log.Error("drop partition update meta fail",
|
|
zap.Int64("collectionID", collectionInfo.CollectionID),
|
|
zap.Int64("indexID", dropIdxID),
|
|
zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) DropCredential(ctx context.Context, username string) error {
|
|
k := fmt.Sprintf("%s/%s", CredentialPrefix, username)
|
|
err := kc.Txn.Remove(k)
|
|
if err != nil {
|
|
log.Error("drop credential update meta fail", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) DropAlias(ctx context.Context, collectionID typeutil.UniqueID, alias string, ts typeutil.Timestamp) error {
|
|
delMetakeys := []string{
|
|
fmt.Sprintf("%s/%s", CollectionAliasMetaPrefix, alias),
|
|
}
|
|
|
|
meta := make(map[string]string)
|
|
err := kc.Snapshot.MultiSaveAndRemoveWithPrefix(meta, delMetakeys, ts)
|
|
if err != nil {
|
|
log.Error("drop alias update meta fail", zap.String("alias", alias), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) GetCollectionByName(ctx context.Context, collectionName string, ts typeutil.Timestamp) (*model.Collection, error) {
|
|
_, vals, err := kc.Snapshot.LoadWithPrefix(CollectionMetaPrefix, ts)
|
|
if err != nil {
|
|
log.Warn("get collection meta fail", zap.String("collectionName", collectionName), zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
for _, val := range vals {
|
|
colMeta := pb.CollectionInfo{}
|
|
err = proto.Unmarshal([]byte(val), &colMeta)
|
|
if err != nil {
|
|
log.Warn("get collection meta unmarshal fail", zap.String("collectionName", collectionName), zap.Error(err))
|
|
continue
|
|
}
|
|
if colMeta.Schema.Name == collectionName {
|
|
return model.UnmarshalCollectionModel(&colMeta), nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("can't find collection: %s, at timestamp = %d", collectionName, ts)
|
|
}
|
|
|
|
func (kc *Catalog) ListCollections(ctx context.Context, ts typeutil.Timestamp) (map[string]*model.Collection, error) {
|
|
_, vals, err := kc.Snapshot.LoadWithPrefix(CollectionMetaPrefix, ts)
|
|
if err != nil {
|
|
log.Error("get collections meta fail",
|
|
zap.String("prefix", CollectionMetaPrefix),
|
|
zap.Uint64("timestamp", ts),
|
|
zap.Error(err))
|
|
return nil, nil
|
|
}
|
|
|
|
colls := make(map[string]*model.Collection)
|
|
for _, val := range vals {
|
|
collMeta := pb.CollectionInfo{}
|
|
err := proto.Unmarshal([]byte(val), &collMeta)
|
|
if err != nil {
|
|
log.Warn("unmarshal collection info failed", zap.Error(err))
|
|
continue
|
|
}
|
|
colls[collMeta.Schema.Name] = model.UnmarshalCollectionModel(&collMeta)
|
|
}
|
|
|
|
return colls, nil
|
|
}
|
|
|
|
func (kc *Catalog) ListAliases(ctx context.Context) ([]*model.Collection, error) {
|
|
_, values, err := kc.Snapshot.LoadWithPrefix(CollectionAliasMetaPrefix, 0)
|
|
if err != nil {
|
|
log.Error("get aliases meta fail", zap.String("prefix", CollectionAliasMetaPrefix), zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
var colls []*model.Collection
|
|
for _, value := range values {
|
|
aliasInfo := pb.CollectionInfo{}
|
|
err = proto.Unmarshal([]byte(value), &aliasInfo)
|
|
if err != nil {
|
|
log.Warn("unmarshal aliases failed", zap.Error(err))
|
|
continue
|
|
}
|
|
colls = append(colls, model.UnmarshalCollectionModel(&aliasInfo))
|
|
}
|
|
|
|
return colls, nil
|
|
}
|
|
|
|
func (kc *Catalog) listSegmentIndexes(ctx context.Context) (map[int64]*model.Index, error) {
|
|
_, values, err := kc.Txn.LoadWithPrefix(SegmentIndexMetaPrefix)
|
|
if err != nil {
|
|
log.Error("list segment index meta fail", zap.String("prefix", SegmentIndexMetaPrefix), zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
indexes := make(map[int64]*model.Index, len(values))
|
|
for _, value := range values {
|
|
if bytes.Equal([]byte(value), SuffixSnapshotTombstone) {
|
|
// backward compatibility, IndexMeta used to be in SnapshotKV
|
|
continue
|
|
}
|
|
segmentIndexInfo := pb.SegmentIndexInfo{}
|
|
err = proto.Unmarshal([]byte(value), &segmentIndexInfo)
|
|
if err != nil {
|
|
log.Warn("unmarshal segment index info failed", zap.Error(err))
|
|
continue
|
|
}
|
|
|
|
newIndex := model.UnmarshalSegmentIndexModel(&segmentIndexInfo)
|
|
oldIndex, ok := indexes[segmentIndexInfo.IndexID]
|
|
if ok {
|
|
for segID, segmentIdxInfo := range newIndex.SegmentIndexes {
|
|
oldIndex.SegmentIndexes[segID] = segmentIdxInfo
|
|
}
|
|
} else {
|
|
indexes[segmentIndexInfo.IndexID] = newIndex
|
|
}
|
|
}
|
|
|
|
return indexes, nil
|
|
}
|
|
|
|
func (kc *Catalog) listIndexMeta(ctx context.Context) (map[int64]*model.Index, error) {
|
|
_, values, err := kc.Txn.LoadWithPrefix(IndexMetaPrefix)
|
|
if err != nil {
|
|
log.Error("list index meta fail", zap.String("prefix", IndexMetaPrefix), zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
indexes := make(map[int64]*model.Index, len(values))
|
|
for _, value := range values {
|
|
if bytes.Equal([]byte(value), SuffixSnapshotTombstone) {
|
|
// backward compatibility, IndexMeta used to be in SnapshotKV
|
|
continue
|
|
}
|
|
meta := pb.IndexInfo{}
|
|
err = proto.Unmarshal([]byte(value), &meta)
|
|
if err != nil {
|
|
log.Warn("unmarshal index info failed", zap.Error(err))
|
|
continue
|
|
}
|
|
|
|
index := model.UnmarshalIndexModel(&meta)
|
|
if _, ok := indexes[meta.IndexID]; ok {
|
|
log.Warn("duplicated index id exists in index meta", zap.Int64("index id", meta.IndexID))
|
|
}
|
|
|
|
indexes[meta.IndexID] = index
|
|
}
|
|
|
|
return indexes, nil
|
|
}
|
|
|
|
func (kc *Catalog) ListIndexes(ctx context.Context) ([]*model.Index, error) {
|
|
indexMeta, err := kc.listIndexMeta(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
segmentIndexMeta, err := kc.listSegmentIndexes(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var indexes []*model.Index
|
|
//merge index and segment index
|
|
for indexID, index := range indexMeta {
|
|
segmentIndex, ok := segmentIndexMeta[indexID]
|
|
if ok {
|
|
index = model.MergeIndexModel(index, segmentIndex)
|
|
delete(segmentIndexMeta, indexID)
|
|
}
|
|
indexes = append(indexes, index)
|
|
}
|
|
|
|
// add remain segmentIndexMeta
|
|
for _, index := range segmentIndexMeta {
|
|
indexes = append(indexes, index)
|
|
}
|
|
|
|
return indexes, nil
|
|
}
|
|
|
|
func (kc *Catalog) ListCredentials(ctx context.Context) ([]string, error) {
|
|
keys, _, err := kc.Txn.LoadWithPrefix(CredentialPrefix)
|
|
if err != nil {
|
|
log.Error("list all credential usernames fail", zap.String("prefix", CredentialPrefix), zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
var usernames []string
|
|
for _, path := range keys {
|
|
username := typeutil.After(path, UserSubPrefix+"/")
|
|
if len(username) == 0 {
|
|
log.Warn("no username extract from path:", zap.String("path", path))
|
|
continue
|
|
}
|
|
usernames = append(usernames, username)
|
|
}
|
|
|
|
return usernames, nil
|
|
}
|
|
|
|
func (kc *Catalog) CreateRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity) error {
|
|
k := funcutil.HandleTenantForEtcdKey(RolePrefix, tenant, entity.Name)
|
|
err := kc.Txn.Save(k, "")
|
|
if err != nil {
|
|
log.Error("fail to create role", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) DropRole(ctx context.Context, tenant string, roleName string) error {
|
|
k := funcutil.HandleTenantForEtcdKey(RolePrefix, tenant, roleName)
|
|
err := kc.Txn.Remove(k)
|
|
if err != nil {
|
|
log.Error("fail to drop role", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) OperateUserRole(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.Txn.Save(k, "")
|
|
if err != nil {
|
|
log.Error("fail to add user to role", zap.String("key", k), zap.Error(err))
|
|
}
|
|
} else if operateType == milvuspb.OperateUserRoleType_RemoveUserFromRole {
|
|
err = kc.Txn.Remove(k)
|
|
if err != nil {
|
|
log.Error("fail to remove user from role", zap.String("key", k), zap.Error(err))
|
|
}
|
|
} else {
|
|
err = fmt.Errorf("invalid operate user role type, operate type: %d", operateType)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (kc *Catalog) SelectRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) {
|
|
var results []*milvuspb.RoleResult
|
|
|
|
roleToUsers := make(map[string][]string)
|
|
if includeUserInfo {
|
|
roleMappingKey := funcutil.HandleTenantForEtcdKey(RoleMappingPrefix, tenant, "")
|
|
keys, _, err := kc.Txn.LoadWithPrefix(roleMappingKey)
|
|
if err != nil {
|
|
log.Error("fail to load role mappings", zap.String("key", roleMappingKey), zap.Error(err))
|
|
return results, err
|
|
}
|
|
|
|
for _, key := range keys {
|
|
roleMappingInfos := typeutil.AfterN(key, roleMappingKey+"/", "/")
|
|
if len(roleMappingInfos) != 2 {
|
|
log.Warn("invalid role mapping key", zap.String("key", key))
|
|
continue
|
|
}
|
|
username := roleMappingInfos[0]
|
|
roleName := roleMappingInfos[1]
|
|
roleToUsers[roleName] = append(roleToUsers[roleName], username)
|
|
}
|
|
}
|
|
|
|
appendRoleResult := func(roleName string) {
|
|
var users []*milvuspb.UserEntity
|
|
for _, username := range roleToUsers[roleName] {
|
|
users = append(users, &milvuspb.UserEntity{Name: username})
|
|
}
|
|
results = append(results, &milvuspb.RoleResult{
|
|
Role: &milvuspb.RoleEntity{Name: roleName},
|
|
Users: users,
|
|
})
|
|
}
|
|
|
|
if entity == nil {
|
|
roleKey := funcutil.HandleTenantForEtcdKey(RolePrefix, tenant, "")
|
|
keys, _, err := kc.Txn.LoadWithPrefix(roleKey)
|
|
if err != nil {
|
|
log.Error("fail to load roles", zap.String("key", roleKey), zap.Error(err))
|
|
return results, err
|
|
}
|
|
for _, key := range keys {
|
|
infoArr := typeutil.AfterN(key, roleKey+"/", "/")
|
|
if len(infoArr) != 1 || len(infoArr[0]) == 0 {
|
|
log.Warn("invalid role key", zap.String("key", key))
|
|
continue
|
|
}
|
|
appendRoleResult(infoArr[0])
|
|
}
|
|
} else {
|
|
if funcutil.IsEmptyString(entity.Name) {
|
|
return results, fmt.Errorf("role name in the role entity is empty")
|
|
}
|
|
roleKey := funcutil.HandleTenantForEtcdKey(RolePrefix, tenant, entity.Name)
|
|
_, err := kc.Txn.Load(roleKey)
|
|
if err != nil {
|
|
log.Error("fail to load a role", zap.String("key", roleKey), zap.Error(err))
|
|
return results, err
|
|
}
|
|
appendRoleResult(entity.Name)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (kc *Catalog) getRolesByUsername(tenant string, username string) ([]string, error) {
|
|
var roles []string
|
|
k := funcutil.HandleTenantForEtcdKey(RoleMappingPrefix, tenant, username)
|
|
keys, _, err := kc.Txn.LoadWithPrefix(k)
|
|
if err != nil {
|
|
log.Error("fail to load role mappings by the username", zap.String("key", k), zap.Error(err))
|
|
return roles, err
|
|
}
|
|
for _, key := range keys {
|
|
roleMappingInfos := typeutil.AfterN(key, k+"/", "/")
|
|
if len(roleMappingInfos) != 1 {
|
|
log.Warn("invalid role mapping key", zap.String("key", key))
|
|
continue
|
|
}
|
|
roles = append(roles, roleMappingInfos[0])
|
|
}
|
|
return roles, nil
|
|
}
|
|
|
|
// getUserResult get the user result by the username. And never return the error because the error means the user isn't added to a role.
|
|
func (kc *Catalog) getUserResult(tenant string, username string, includeRoleInfo bool) (*milvuspb.UserResult, error) {
|
|
result := &milvuspb.UserResult{User: &milvuspb.UserEntity{Name: username}}
|
|
if !includeRoleInfo {
|
|
return result, nil
|
|
}
|
|
roleNames, err := kc.getRolesByUsername(tenant, username)
|
|
if err != nil {
|
|
log.Warn("fail to get roles by the username", zap.Error(err))
|
|
return result, err
|
|
}
|
|
var roles []*milvuspb.RoleEntity
|
|
for _, roleName := range roleNames {
|
|
roles = append(roles, &milvuspb.RoleEntity{Name: roleName})
|
|
}
|
|
result.Roles = roles
|
|
return result, nil
|
|
}
|
|
|
|
func (kc *Catalog) SelectUser(ctx context.Context, tenant string, entity *milvuspb.UserEntity, includeRoleInfo bool) ([]*milvuspb.UserResult, error) {
|
|
var (
|
|
usernames []string
|
|
err error
|
|
results []*milvuspb.UserResult
|
|
)
|
|
|
|
appendUserResult := func(username string) error {
|
|
result, err := kc.getUserResult(tenant, username, includeRoleInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
results = append(results, result)
|
|
return nil
|
|
}
|
|
|
|
if entity == nil {
|
|
usernames, err = kc.ListCredentials(ctx)
|
|
if err != nil {
|
|
return results, err
|
|
}
|
|
} else {
|
|
if funcutil.IsEmptyString(entity.Name) {
|
|
return results, fmt.Errorf("username in the user entity is empty")
|
|
}
|
|
_, err = kc.GetCredential(ctx, entity.Name)
|
|
if err != nil {
|
|
return results, err
|
|
}
|
|
usernames = append(usernames, entity.Name)
|
|
}
|
|
for _, username := range usernames {
|
|
err = appendUserResult(username)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (kc *Catalog) OperatePrivilege(ctx context.Context, tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error {
|
|
privilegeName := entity.Grantor.Privilege.Name
|
|
k := funcutil.HandleTenantForEtcdKey(GranteePrefix, tenant, fmt.Sprintf("%s/%s/%s", entity.Role.Name, entity.Object.Name, entity.ObjectName))
|
|
|
|
curGrantPrivilegeEntity := &milvuspb.GrantPrivilegeEntity{}
|
|
v, err := kc.Txn.Load(k)
|
|
if err != nil {
|
|
log.Warn("fail to load grant privilege entity", zap.String("key", k), zap.Any("type", operateType), zap.Error(err))
|
|
if funcutil.IsRevoke(operateType) {
|
|
return err
|
|
}
|
|
if !funcutil.IsKeyNotExistError(err) {
|
|
return err
|
|
}
|
|
curGrantPrivilegeEntity.Entities = append(curGrantPrivilegeEntity.Entities, &milvuspb.GrantorEntity{
|
|
Privilege: &milvuspb.PrivilegeEntity{Name: privilegeName},
|
|
User: &milvuspb.UserEntity{Name: entity.Grantor.User.Name},
|
|
})
|
|
} else {
|
|
err = proto.Unmarshal([]byte(v), curGrantPrivilegeEntity)
|
|
if err != nil {
|
|
log.Error("fail to unmarshal the grant privilege entity", zap.String("key", k), zap.Any("type", operateType), zap.Error(err))
|
|
return err
|
|
}
|
|
isExisted := false
|
|
dropIndex := -1
|
|
|
|
for entityIndex, grantorEntity := range curGrantPrivilegeEntity.Entities {
|
|
if grantorEntity.Privilege.Name == privilegeName {
|
|
isExisted = true
|
|
dropIndex = entityIndex
|
|
break
|
|
}
|
|
}
|
|
if !isExisted && funcutil.IsGrant(operateType) {
|
|
curGrantPrivilegeEntity.Entities = append(curGrantPrivilegeEntity.Entities, &milvuspb.GrantorEntity{
|
|
Privilege: &milvuspb.PrivilegeEntity{Name: privilegeName},
|
|
User: &milvuspb.UserEntity{Name: entity.Grantor.User.Name},
|
|
})
|
|
} else if isExisted && funcutil.IsGrant(operateType) {
|
|
return nil
|
|
} else if !isExisted && funcutil.IsRevoke(operateType) {
|
|
return fmt.Errorf("fail to revoke the privilege because the privilege isn't granted for the role, key: /%s", k)
|
|
} else if isExisted && funcutil.IsRevoke(operateType) {
|
|
curGrantPrivilegeEntity.Entities = append(curGrantPrivilegeEntity.Entities[:dropIndex], curGrantPrivilegeEntity.Entities[dropIndex+1:]...)
|
|
}
|
|
}
|
|
|
|
if funcutil.IsRevoke(operateType) && len(curGrantPrivilegeEntity.Entities) == 0 {
|
|
err = kc.Txn.Remove(k)
|
|
if err != nil {
|
|
log.Error("fail to remove the grant privilege entity", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
saveValue, err := proto.Marshal(curGrantPrivilegeEntity)
|
|
if err != nil {
|
|
log.Error("fail to marshal the grant privilege entity", zap.String("key", k), zap.Any("type", operateType), zap.Error(err))
|
|
return fmt.Errorf("fail to marshal grant info, key:%s, err:%w", k, err)
|
|
}
|
|
err = kc.Txn.Save(k, string(saveValue))
|
|
if err != nil {
|
|
log.Error("fail to save the grant privilege entity", zap.String("key", k), zap.Any("type", operateType), zap.Error(err))
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (kc *Catalog) SelectGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity) ([]*milvuspb.GrantEntity, error) {
|
|
var entities []*milvuspb.GrantEntity
|
|
|
|
var k string
|
|
appendGrantEntity := func(v string, object string, objectName string) error {
|
|
grantPrivilegeEntity := &milvuspb.GrantPrivilegeEntity{}
|
|
err := proto.Unmarshal([]byte(v), grantPrivilegeEntity)
|
|
if err != nil {
|
|
log.Error("fail to unmarshal the grant privilege entity", zap.String("key", k), zap.Error(err))
|
|
return err
|
|
}
|
|
for _, grantorEntity := range grantPrivilegeEntity.Entities {
|
|
entities = append(entities, &milvuspb.GrantEntity{
|
|
Role: &milvuspb.RoleEntity{Name: entity.Role.Name},
|
|
Object: &milvuspb.ObjectEntity{Name: object},
|
|
ObjectName: objectName,
|
|
Grantor: &milvuspb.GrantorEntity{
|
|
User: &milvuspb.UserEntity{Name: grantorEntity.User.Name},
|
|
Privilege: &milvuspb.PrivilegeEntity{Name: util.PrivilegeNameForAPI(grantorEntity.Privilege.Name)},
|
|
},
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if !funcutil.IsEmptyString(entity.ObjectName) && entity.Object != nil && !funcutil.IsEmptyString(entity.Object.Name) {
|
|
k = funcutil.HandleTenantForEtcdKey(GranteePrefix, tenant, fmt.Sprintf("%s/%s/%s", entity.Role.Name, entity.Object.Name, entity.ObjectName))
|
|
v, err := kc.Txn.Load(k)
|
|
if err != nil {
|
|
log.Error("fail to load the grant privilege entity", zap.String("key", k), zap.Error(err))
|
|
return entities, err
|
|
}
|
|
err = appendGrantEntity(v, entity.Object.Name, entity.ObjectName)
|
|
if err != nil {
|
|
return entities, err
|
|
}
|
|
} else {
|
|
k = funcutil.HandleTenantForEtcdKey(GranteePrefix, tenant, entity.Role.Name)
|
|
keys, values, err := kc.Txn.LoadWithPrefix(k)
|
|
if err != nil {
|
|
log.Error("fail to load grant privilege entities", zap.String("key", k), zap.Error(err))
|
|
return entities, err
|
|
}
|
|
for i, key := range keys {
|
|
grantInfos := typeutil.AfterN(key, k+"/", "/")
|
|
if len(grantInfos) != 2 {
|
|
log.Warn("invalid grant key", zap.String("key", key))
|
|
continue
|
|
}
|
|
err = appendGrantEntity(values[i], grantInfos[0], grantInfos[1])
|
|
if err != nil {
|
|
return entities, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return entities, nil
|
|
}
|
|
|
|
func (kc *Catalog) ListPolicy(ctx context.Context, tenant string) ([]string, error) {
|
|
var grantInfoStrs []string
|
|
k := funcutil.HandleTenantForEtcdKey(GranteePrefix, tenant, "")
|
|
keys, values, err := kc.Txn.LoadWithPrefix(k)
|
|
if err != nil {
|
|
log.Error("fail to load all grant privilege entities", zap.String("key", k), zap.Error(err))
|
|
return []string{}, err
|
|
}
|
|
|
|
for i, key := range keys {
|
|
grantInfos := typeutil.AfterN(key, k+"/", "/")
|
|
if len(grantInfos) != 3 {
|
|
log.Warn("invalid grant key", zap.String("key", key))
|
|
continue
|
|
}
|
|
grantPrivilegeEntity := &milvuspb.GrantPrivilegeEntity{}
|
|
err = proto.Unmarshal([]byte(values[i]), grantPrivilegeEntity)
|
|
if err != nil {
|
|
log.Warn("fail to unmarshal the grant privilege entity", zap.String("key", key), zap.Error(err))
|
|
continue
|
|
}
|
|
for _, grantorInfo := range grantPrivilegeEntity.Entities {
|
|
grantInfoStrs = append(grantInfoStrs,
|
|
funcutil.PolicyForPrivilege(grantInfos[0], grantInfos[1], grantInfos[2], grantorInfo.Privilege.Name))
|
|
}
|
|
}
|
|
return grantInfoStrs, nil
|
|
}
|
|
|
|
func (kc *Catalog) ListUserRole(ctx context.Context, tenant string) ([]string, error) {
|
|
var userRoles []string
|
|
k := funcutil.HandleTenantForEtcdKey(RoleMappingPrefix, tenant, "")
|
|
keys, _, err := kc.Txn.LoadWithPrefix(k)
|
|
if err != nil {
|
|
log.Error("fail to load all user-role mappings", zap.String("key", k), zap.Error(err))
|
|
return []string{}, err
|
|
}
|
|
|
|
for _, key := range keys {
|
|
userRolesInfos := typeutil.AfterN(key, k+"/", "/")
|
|
if len(userRolesInfos) != 2 {
|
|
log.Warn("invalid user-role key", zap.String("key", key))
|
|
continue
|
|
}
|
|
userRoles = append(userRoles, funcutil.EncodeUserRoleCache(userRolesInfos[0], userRolesInfos[1]))
|
|
}
|
|
return userRoles, nil
|
|
}
|
|
|
|
func (kc *Catalog) Close() {
|
|
// do nothing
|
|
}
|