package ticktock import ( "context" "fmt" "strconv" "github.com/samber/lo" "gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec" "gitlink.org.cn/cloudream/common/pkgs/logger" "gitlink.org.cn/cloudream/common/utils/math2" "gitlink.org.cn/cloudream/common/utils/sort2" "gitlink.org.cn/cloudream/jcs-pub/client/internal/db" clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types" "gitlink.org.cn/cloudream/jcs-pub/common/models/datamap" "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2" "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/ops2" "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/parser" "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitchlrc" lrcparser "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitchlrc/parser" cortypes "gitlink.org.cn/cloudream/jcs-pub/coordinator/types" ) func (t *ChangeRedundancy) chooseRedundancy(ctx *changeRedundancyContext, obj clitypes.ObjectDetail) (clitypes.Redundancy, []*userSpaceLoadInfo) { switch obj.Object.Redundancy.(type) { case *clitypes.NoneRedundancy: if obj.Object.Size > ctx.ticktock.cfg.ECFileSizeThreshold { newStgs := t.chooseNewUserSpacesForEC(ctx, &clitypes.DefaultECRedundancy) return &clitypes.DefaultECRedundancy, newStgs } return &clitypes.DefaultRepRedundancy, t.chooseNewUserSpacesForRep(ctx, &clitypes.DefaultRepRedundancy) case *clitypes.RepRedundancy: if obj.Object.Size >= ctx.ticktock.cfg.ECFileSizeThreshold { newStgs := t.chooseNewUserSpacesForEC(ctx, &clitypes.DefaultECRedundancy) return &clitypes.DefaultECRedundancy, newStgs } return clitypes.DefaultRepRedundancy, t.rechooseUserSpacesForRep(ctx, &clitypes.DefaultRepRedundancy) case *clitypes.ECRedundancy: if obj.Object.Size < ctx.ticktock.cfg.ECFileSizeThreshold { return &clitypes.DefaultRepRedundancy, t.chooseNewUserSpacesForRep(ctx, &clitypes.DefaultRepRedundancy) } return clitypes.DefaultECRedundancy, t.rechooseUserSpacesForEC(ctx, obj, &clitypes.DefaultECRedundancy) case *clitypes.LRCRedundancy: newLRCStgs := t.rechooseUserSpacesForLRC(ctx, obj, &clitypes.DefaultLRCRedundancy) return &clitypes.DefaultLRCRedundancy, newLRCStgs } return nil, nil } func (t *ChangeRedundancy) doChangeRedundancy(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, newRed clitypes.Redundancy, selectedUserSpaces []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) { log := logger.WithType[ChangeRedundancy]("TickTock") var updating *db.UpdatingObjectRedundancy var evt datamap.SysEventBody var err error switch srcRed := obj.Object.Redundancy.(type) { case *clitypes.NoneRedundancy: switch newRed := newRed.(type) { case *clitypes.RepRedundancy: log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> rep") updating, evt, err = t.noneToRep(ctx, obj, newRed, selectedUserSpaces) case *clitypes.ECRedundancy: log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> ec") updating, evt, err = t.noneToEC(ctx, obj, newRed, selectedUserSpaces) case *clitypes.LRCRedundancy: log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> lrc") updating, evt, err = t.noneToLRC(ctx, obj, newRed, selectedUserSpaces) case *clitypes.SegmentRedundancy: log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> segment") updating, evt, err = t.noneToSeg(ctx, obj, newRed, selectedUserSpaces) } case *clitypes.RepRedundancy: switch newRed := newRed.(type) { case *clitypes.RepRedundancy: updating, evt, err = t.repToRep(ctx, obj, srcRed, selectedUserSpaces) case *clitypes.ECRedundancy: log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: rep -> ec") updating, evt, err = t.repToEC(ctx, obj, newRed, selectedUserSpaces) } case *clitypes.ECRedundancy: switch newRed := newRed.(type) { case *clitypes.RepRedundancy: log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: ec -> rep") updating, evt, err = t.ecToRep(ctx, obj, srcRed, newRed, selectedUserSpaces) case *clitypes.ECRedundancy: updating, evt, err = t.ecToEC(ctx, obj, srcRed, newRed, selectedUserSpaces) } case *clitypes.LRCRedundancy: switch newRed := newRed.(type) { case *clitypes.LRCRedundancy: updating, evt, err = t.lrcToLRC(ctx, obj, srcRed, newRed, selectedUserSpaces) } } return updating, evt, err } // 统计每个对象块所在的节点,选出块最多的不超过userspaceCnt个节点 func (t *ChangeRedundancy) summaryRepObjectBlockUserSpaces(ctx *changeRedundancyContext, objs []clitypes.ObjectDetail, userspaceCnt int) []clitypes.UserSpaceID { type stgBlocks struct { UserSpaceID clitypes.UserSpaceID Count int } stgBlocksMap := make(map[clitypes.UserSpaceID]*stgBlocks) for _, obj := range objs { shouldUseEC := obj.Object.Size > ctx.ticktock.cfg.ECFileSizeThreshold if _, ok := obj.Object.Redundancy.(*clitypes.RepRedundancy); ok && !shouldUseEC { for _, block := range obj.Blocks { if _, ok := stgBlocksMap[block.UserSpaceID]; !ok { stgBlocksMap[block.UserSpaceID] = &stgBlocks{ UserSpaceID: block.UserSpaceID, Count: 0, } } stgBlocksMap[block.UserSpaceID].Count++ } } } userspaces := lo.Values(stgBlocksMap) sort2.Sort(userspaces, func(left *stgBlocks, right *stgBlocks) int { return right.Count - left.Count }) ids := lo.Map(userspaces, func(item *stgBlocks, idx int) clitypes.UserSpaceID { return item.UserSpaceID }) if len(ids) > userspaceCnt { ids = ids[:userspaceCnt] } return ids } func (t *ChangeRedundancy) chooseNewUserSpacesForRep(ctx *changeRedundancyContext, red *clitypes.RepRedundancy) []*userSpaceLoadInfo { sortedUserSpaces := sort2.Sort(lo.Values(ctx.allUserSpaces), func(left *userSpaceLoadInfo, right *userSpaceLoadInfo) int { return sort2.Cmp(right.AccessAmount, left.AccessAmount) }) return t.chooseSoManyUserSpaces(red.RepCount, sortedUserSpaces) } func (t *ChangeRedundancy) chooseNewUserSpacesForEC(ctx *changeRedundancyContext, red *clitypes.ECRedundancy) []*userSpaceLoadInfo { sortedUserSpaces := sort2.Sort(lo.Values(ctx.allUserSpaces), func(left *userSpaceLoadInfo, right *userSpaceLoadInfo) int { return sort2.Cmp(right.AccessAmount, left.AccessAmount) }) return t.chooseSoManyUserSpaces(red.N, sortedUserSpaces) } func (t *ChangeRedundancy) chooseNewUserSpacesForLRC(ctx *changeRedundancyContext, red *clitypes.LRCRedundancy) []*userSpaceLoadInfo { sortedUserSpaces := sort2.Sort(lo.Values(ctx.allUserSpaces), func(left *userSpaceLoadInfo, right *userSpaceLoadInfo) int { return sort2.Cmp(right.AccessAmount, left.AccessAmount) }) return t.chooseSoManyUserSpaces(red.N, sortedUserSpaces) } func (t *ChangeRedundancy) chooseNewUserSpacesForSeg(ctx *changeRedundancyContext, segCount int) []*userSpaceLoadInfo { sortedUserSpaces := sort2.Sort(lo.Values(ctx.allUserSpaces), func(left *userSpaceLoadInfo, right *userSpaceLoadInfo) int { return sort2.Cmp(right.AccessAmount, left.AccessAmount) }) return t.chooseSoManyUserSpaces(segCount, sortedUserSpaces) } func (t *ChangeRedundancy) rechooseUserSpacesForRep(ctx *changeRedundancyContext, red *clitypes.RepRedundancy) []*userSpaceLoadInfo { type rechooseUserSpace struct { *userSpaceLoadInfo HasBlock bool } var rechooseStgs []*rechooseUserSpace for _, stg := range ctx.allUserSpaces { hasBlock := false for _, id := range ctx.mostBlockStgIDs { if id == stg.UserSpace.UserSpace.UserSpaceID { hasBlock = true break } } rechooseStgs = append(rechooseStgs, &rechooseUserSpace{ userSpaceLoadInfo: stg, HasBlock: hasBlock, }) } sortedStgs := sort2.Sort(rechooseStgs, func(left *rechooseUserSpace, right *rechooseUserSpace) int { // 已经缓存了文件块的节点优先选择 v := sort2.CmpBool(right.HasBlock, left.HasBlock) if v != 0 { return v } return sort2.Cmp(right.AccessAmount, left.AccessAmount) }) return t.chooseSoManyUserSpaces(red.RepCount, lo.Map(sortedStgs, func(userspace *rechooseUserSpace, idx int) *userSpaceLoadInfo { return userspace.userSpaceLoadInfo })) } func (t *ChangeRedundancy) rechooseUserSpacesForEC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.ECRedundancy) []*userSpaceLoadInfo { type rechooseStg struct { *userSpaceLoadInfo CachedBlockIndex int } var rechooseStgs []*rechooseStg for _, stg := range ctx.allUserSpaces { cachedBlockIndex := -1 for _, block := range obj.Blocks { if block.UserSpaceID == stg.UserSpace.UserSpace.UserSpaceID { cachedBlockIndex = block.Index break } } rechooseStgs = append(rechooseStgs, &rechooseStg{ userSpaceLoadInfo: stg, CachedBlockIndex: cachedBlockIndex, }) } sortedStgs := sort2.Sort(rechooseStgs, func(left *rechooseStg, right *rechooseStg) int { // 已经缓存了文件块的节点优先选择 v := sort2.CmpBool(right.CachedBlockIndex > -1, left.CachedBlockIndex > -1) if v != 0 { return v } return sort2.Cmp(right.AccessAmount, left.AccessAmount) }) // TODO 可以考虑选择已有块的节点时,能依然按照Index顺序选择 return t.chooseSoManyUserSpaces(red.N, lo.Map(sortedStgs, func(userspace *rechooseStg, idx int) *userSpaceLoadInfo { return userspace.userSpaceLoadInfo })) } func (t *ChangeRedundancy) rechooseUserSpacesForLRC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.LRCRedundancy) []*userSpaceLoadInfo { type rechooseStg struct { *userSpaceLoadInfo CachedBlockIndex int } var rechooseStgs []*rechooseStg for _, stg := range ctx.allUserSpaces { cachedBlockIndex := -1 for _, block := range obj.Blocks { if block.UserSpaceID == stg.UserSpace.UserSpace.UserSpaceID { cachedBlockIndex = block.Index break } } rechooseStgs = append(rechooseStgs, &rechooseStg{ userSpaceLoadInfo: stg, CachedBlockIndex: cachedBlockIndex, }) } sortedStgs := sort2.Sort(rechooseStgs, func(left *rechooseStg, right *rechooseStg) int { // 已经缓存了文件块的节点优先选择 v := sort2.CmpBool(right.CachedBlockIndex > -1, left.CachedBlockIndex > -1) if v != 0 { return v } return sort2.Cmp(right.AccessAmount, left.AccessAmount) }) // TODO 可以考虑选择已有块的节点时,能依然按照Index顺序选择 return t.chooseSoManyUserSpaces(red.N, lo.Map(sortedStgs, func(userspace *rechooseStg, idx int) *userSpaceLoadInfo { return userspace.userSpaceLoadInfo })) } func (t *ChangeRedundancy) chooseSoManyUserSpaces(count int, stgs []*userSpaceLoadInfo) []*userSpaceLoadInfo { repeateCount := (count + len(stgs) - 1) / len(stgs) extendStgs := make([]*userSpaceLoadInfo, repeateCount*len(stgs)) // 使用复制的方式将节点数扩充到要求的数量 // 复制之后的结构:ABCD -> AAABBBCCCDDD for p := 0; p < repeateCount; p++ { for i, userspace := range stgs { putIdx := i*repeateCount + p extendStgs[putIdx] = userspace } } extendStgs = extendStgs[:count] var chosen []*userSpaceLoadInfo for len(chosen) < count { // 在每一轮内都选不同地区的节点,如果节点数不够,那么就再来一轮 chosenLocations := make(map[cortypes.LocationID]bool) for i, stg := range extendStgs { if stg == nil { continue } if chosenLocations[stg.UserSpace.MasterHub.LocationID] { continue } chosen = append(chosen, stg) chosenLocations[stg.UserSpace.MasterHub.LocationID] = true extendStgs[i] = nil } } return chosen } func (t *ChangeRedundancy) noneToRep(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.RepRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) { if len(obj.Blocks) == 0 { return nil, nil, fmt.Errorf("object is not cached on any userspaces, cannot change its redundancy to rep") } srcStg, ok := ctx.allUserSpaces[obj.Blocks[0].UserSpaceID] if !ok { return nil, nil, fmt.Errorf("userspace %v not found", obj.Blocks[0].UserSpaceID) } if srcStg.UserSpace.MasterHub == nil { return nil, nil, fmt.Errorf("userspace %v has no master hub", obj.Blocks[0].UserSpaceID) } // 如果选择的备份节点都是同一个,那么就只要上传一次 uploadStgs = lo.UniqBy(uploadStgs, func(item *userSpaceLoadInfo) clitypes.UserSpaceID { return item.UserSpace.UserSpace.UserSpaceID }) ft := ioswitch2.NewFromTo() ft.AddFrom(ioswitch2.NewFromShardstore(obj.Object.FileHash, *srcStg.UserSpace.MasterHub, *srcStg.UserSpace, ioswitch2.RawStream())) for i, stg := range uploadStgs { ft.AddTo(ioswitch2.NewToShardStore(*stg.UserSpace.MasterHub, *stg.UserSpace, ioswitch2.RawStream(), fmt.Sprintf("%d", i))) } plans := exec.NewPlanBuilder() err := parser.Parse(ft, plans) if err != nil { return nil, nil, fmt.Errorf("parsing plan: %w", err) } // TODO 添加依赖 execCtx := exec.NewExecContext() exec.SetValueByType(execCtx, ctx.ticktock.stgPool) ret, err := plans.Execute(execCtx).Wait(context.Background()) if err != nil { return nil, nil, fmt.Errorf("executing io plan: %w", err) } var blocks []clitypes.ObjectBlock var blockChgs []datamap.BlockChange for i, stg := range uploadStgs { r := ret[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue) blocks = append(blocks, clitypes.ObjectBlock{ ObjectID: obj.Object.ObjectID, Index: 0, UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID, FileHash: r.Hash, Size: r.Size, }) blockChgs = append(blockChgs, &datamap.BlockChangeClone{ BlockType: datamap.BlockTypeRaw, SourceUserSpaceID: obj.Blocks[0].UserSpaceID, TargetUserSpaceID: stg.UserSpace.UserSpace.UserSpaceID, TransferBytes: 1, }) } // 删除原本的文件块 blockChgs = append(blockChgs, &datamap.BlockChangeDeleted{ Index: 0, UserSpaceID: obj.Blocks[0].UserSpaceID, }) return &db.UpdatingObjectRedundancy{ ObjectID: obj.Object.ObjectID, FileHash: obj.Object.FileHash, Size: obj.Object.Size, Redundancy: red, Blocks: blocks, }, &datamap.BodyBlockTransfer{ ObjectID: obj.Object.ObjectID, PackageID: obj.Object.PackageID, BlockChanges: blockChgs, }, nil } func (t *ChangeRedundancy) noneToEC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.ECRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) { if len(obj.Blocks) == 0 { return nil, nil, fmt.Errorf("object is not cached on any userspaces, cannot change its redundancy to ec") } srcStg, ok := ctx.allUserSpaces[obj.Blocks[0].UserSpaceID] if !ok { return nil, nil, fmt.Errorf("userspace %v not found", obj.Blocks[0].UserSpaceID) } if srcStg.UserSpace.MasterHub == nil { return nil, nil, fmt.Errorf("userspace %v has no master hub", obj.Blocks[0].UserSpaceID) } ft := ioswitch2.NewFromTo() ft.ECParam = red ft.AddFrom(ioswitch2.NewFromShardstore(obj.Object.FileHash, *srcStg.UserSpace.MasterHub, *srcStg.UserSpace, ioswitch2.RawStream())) for i := 0; i < red.N; i++ { ft.AddTo(ioswitch2.NewToShardStore(*uploadStgs[i].UserSpace.MasterHub, *uploadStgs[i].UserSpace, ioswitch2.ECStream(i), fmt.Sprintf("%d", i))) } plans := exec.NewPlanBuilder() err := parser.Parse(ft, plans) if err != nil { return nil, nil, fmt.Errorf("parsing plan: %w", err) } execCtx := exec.NewExecContext() exec.SetValueByType(execCtx, ctx.ticktock.stgPool) ioRet, err := plans.Execute(execCtx).Wait(context.Background()) if err != nil { return nil, nil, fmt.Errorf("executing io plan: %w", err) } var blocks []clitypes.ObjectBlock var evtTargetBlocks []datamap.Block var evtBlockTrans []datamap.DataTransfer for i := 0; i < red.N; i++ { r := ioRet[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue) blocks = append(blocks, clitypes.ObjectBlock{ ObjectID: obj.Object.ObjectID, Index: i, UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID, FileHash: r.Hash, Size: r.Size, }) evtTargetBlocks = append(evtTargetBlocks, datamap.Block{ BlockType: datamap.BlockTypeEC, Index: i, UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID, }) evtBlockTrans = append(evtBlockTrans, datamap.DataTransfer{ SourceUserSpaceID: obj.Blocks[0].UserSpaceID, TargetUserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID, TransferBytes: 1, }) } return &db.UpdatingObjectRedundancy{ ObjectID: obj.Object.ObjectID, FileHash: obj.Object.FileHash, Size: obj.Object.Size, Redundancy: red, Blocks: blocks, }, &datamap.BodyBlockTransfer{ ObjectID: obj.Object.ObjectID, PackageID: obj.Object.PackageID, BlockChanges: []datamap.BlockChange{ &datamap.BlockChangeEnDecode{ SourceBlocks: []datamap.Block{{ BlockType: datamap.BlockTypeRaw, UserSpaceID: obj.Blocks[0].UserSpaceID, }}, TargetBlocks: evtTargetBlocks, DataTransfers: evtBlockTrans, }, // 删除原本的文件块 &datamap.BlockChangeDeleted{ Index: 0, UserSpaceID: obj.Blocks[0].UserSpaceID, }, }, }, nil } func (t *ChangeRedundancy) noneToLRC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.LRCRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) { if len(obj.Blocks) == 0 { return nil, nil, fmt.Errorf("object is not cached on any userspaces, cannot change its redundancy to ec") } srcStg, ok := ctx.allUserSpaces[obj.Blocks[0].UserSpaceID] if !ok { return nil, nil, fmt.Errorf("userspace %v not found", obj.Blocks[0].UserSpaceID) } if srcStg.UserSpace.MasterHub == nil { return nil, nil, fmt.Errorf("userspace %v has no master hub", obj.Blocks[0].UserSpaceID) } var toes []ioswitchlrc.To for i := 0; i < red.N; i++ { toes = append(toes, ioswitchlrc.NewToStorage(*uploadStgs[i].UserSpace.MasterHub, *uploadStgs[i].UserSpace, i, fmt.Sprintf("%d", i))) } plans := exec.NewPlanBuilder() err := lrcparser.Encode(ioswitchlrc.NewFromStorage(obj.Object.FileHash, *srcStg.UserSpace.MasterHub, *srcStg.UserSpace, -1), toes, plans) if err != nil { return nil, nil, fmt.Errorf("parsing plan: %w", err) } execCtx := exec.NewExecContext() exec.SetValueByType(execCtx, ctx.ticktock.stgPool) ioRet, err := plans.Execute(execCtx).Wait(context.Background()) if err != nil { return nil, nil, fmt.Errorf("executing io plan: %w", err) } var blocks []clitypes.ObjectBlock var evtTargetBlocks []datamap.Block var evtBlockTrans []datamap.DataTransfer for i := 0; i < red.N; i++ { r := ioRet[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue) blocks = append(blocks, clitypes.ObjectBlock{ ObjectID: obj.Object.ObjectID, Index: i, UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID, FileHash: r.Hash, Size: r.Size, }) evtTargetBlocks = append(evtTargetBlocks, datamap.Block{ BlockType: datamap.BlockTypeEC, Index: i, UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID, }) evtBlockTrans = append(evtBlockTrans, datamap.DataTransfer{ SourceUserSpaceID: obj.Blocks[0].UserSpaceID, TargetUserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID, TransferBytes: 1, }) } return &db.UpdatingObjectRedundancy{ ObjectID: obj.Object.ObjectID, FileHash: obj.Object.FileHash, Size: obj.Object.Size, Redundancy: red, Blocks: blocks, }, &datamap.BodyBlockTransfer{ ObjectID: obj.Object.ObjectID, PackageID: obj.Object.PackageID, BlockChanges: []datamap.BlockChange{ &datamap.BlockChangeEnDecode{ SourceBlocks: []datamap.Block{{ BlockType: datamap.BlockTypeRaw, UserSpaceID: obj.Blocks[0].UserSpaceID, }}, TargetBlocks: evtTargetBlocks, DataTransfers: evtBlockTrans, }, // 删除原本的文件块 &datamap.BlockChangeDeleted{ Index: 0, UserSpaceID: obj.Blocks[0].UserSpaceID, }, }, }, nil } func (t *ChangeRedundancy) noneToSeg(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.SegmentRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) { if len(obj.Blocks) == 0 { return nil, nil, fmt.Errorf("object is not cached on any userspaces, cannot change its redundancy to rep") } srcStg, ok := ctx.allUserSpaces[obj.Blocks[0].UserSpaceID] if !ok { return nil, nil, fmt.Errorf("userspace %v not found", obj.Blocks[0].UserSpaceID) } if srcStg.UserSpace.MasterHub == nil { return nil, nil, fmt.Errorf("userspace %v has no master hub", obj.Blocks[0].UserSpaceID) } // 如果选择的备份节点都是同一个,那么就只要上传一次 uploadStgs = lo.UniqBy(uploadStgs, func(item *userSpaceLoadInfo) clitypes.UserSpaceID { return item.UserSpace.UserSpace.UserSpaceID }) ft := ioswitch2.NewFromTo() ft.SegmentParam = red ft.AddFrom(ioswitch2.NewFromShardstore(obj.Object.FileHash, *srcStg.UserSpace.MasterHub, *srcStg.UserSpace, ioswitch2.RawStream())) for i, stg := range uploadStgs { ft.AddTo(ioswitch2.NewToShardStore(*stg.UserSpace.MasterHub, *stg.UserSpace, ioswitch2.SegmentStream(i), fmt.Sprintf("%d", i))) } plans := exec.NewPlanBuilder() err := parser.Parse(ft, plans) if err != nil { return nil, nil, fmt.Errorf("parsing plan: %w", err) } // TODO 添加依赖 execCtx := exec.NewExecContext() exec.SetValueByType(execCtx, ctx.ticktock.stgPool) ret, err := plans.Execute(execCtx).Wait(context.Background()) if err != nil { return nil, nil, fmt.Errorf("executing io plan: %w", err) } var blocks []clitypes.ObjectBlock var evtTargetBlocks []datamap.Block var evtBlockTrans []datamap.DataTransfer for i, stg := range uploadStgs { r := ret[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue) blocks = append(blocks, clitypes.ObjectBlock{ ObjectID: obj.Object.ObjectID, Index: i, UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID, FileHash: r.Hash, Size: r.Size, }) evtTargetBlocks = append(evtTargetBlocks, datamap.Block{ BlockType: datamap.BlockTypeSegment, Index: i, UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID, }) evtBlockTrans = append(evtBlockTrans, datamap.DataTransfer{ SourceUserSpaceID: obj.Blocks[0].UserSpaceID, TargetUserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID, TransferBytes: 1, }) } return &db.UpdatingObjectRedundancy{ ObjectID: obj.Object.ObjectID, FileHash: obj.Object.FileHash, Size: obj.Object.Size, Redundancy: red, Blocks: blocks, }, &datamap.BodyBlockTransfer{ ObjectID: obj.Object.ObjectID, PackageID: obj.Object.PackageID, BlockChanges: []datamap.BlockChange{ &datamap.BlockChangeEnDecode{ SourceBlocks: []datamap.Block{{ BlockType: datamap.BlockTypeRaw, UserSpaceID: obj.Blocks[0].UserSpaceID, }}, TargetBlocks: evtTargetBlocks, DataTransfers: evtBlockTrans, }, // 删除原本的文件块 &datamap.BlockChangeDeleted{ Index: 0, UserSpaceID: obj.Blocks[0].UserSpaceID, }, }, }, nil } func (t *ChangeRedundancy) repToRep(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.RepRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) { if len(obj.Blocks) == 0 { return nil, nil, fmt.Errorf("object is not cached on any userspaces, cannot change its redundancy to rep") } srcStg, ok := ctx.allUserSpaces[obj.Blocks[0].UserSpaceID] if !ok { return nil, nil, fmt.Errorf("userspace %v not found", obj.Blocks[0].UserSpaceID) } if srcStg.UserSpace.MasterHub == nil { return nil, nil, fmt.Errorf("userspace %v has no master hub", obj.Blocks[0].UserSpaceID) } // 如果选择的备份节点都是同一个,那么就只要上传一次 uploadStgs = lo.UniqBy(uploadStgs, func(item *userSpaceLoadInfo) clitypes.UserSpaceID { return item.UserSpace.UserSpace.UserSpaceID }) ft := ioswitch2.NewFromTo() ft.AddFrom(ioswitch2.NewFromShardstore(obj.Object.FileHash, *srcStg.UserSpace.MasterHub, *srcStg.UserSpace, ioswitch2.RawStream())) for i, stg := range uploadStgs { ft.AddTo(ioswitch2.NewToShardStore(*stg.UserSpace.MasterHub, *stg.UserSpace, ioswitch2.RawStream(), fmt.Sprintf("%d", i))) } plans := exec.NewPlanBuilder() err := parser.Parse(ft, plans) if err != nil { return nil, nil, fmt.Errorf("parsing plan: %w", err) } // TODO 添加依赖 execCtx := exec.NewExecContext() exec.SetValueByType(execCtx, ctx.ticktock.stgPool) ret, err := plans.Execute(execCtx).Wait(context.Background()) if err != nil { return nil, nil, fmt.Errorf("executing io plan: %w", err) } var blocks []clitypes.ObjectBlock var blockChgs []datamap.BlockChange for i, stg := range uploadStgs { r := ret[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue) blocks = append(blocks, clitypes.ObjectBlock{ ObjectID: obj.Object.ObjectID, Index: 0, UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID, FileHash: r.Hash, Size: r.Size, }) blockChgs = append(blockChgs, &datamap.BlockChangeClone{ BlockType: datamap.BlockTypeRaw, SourceUserSpaceID: obj.Blocks[0].UserSpaceID, TargetUserSpaceID: stg.UserSpace.UserSpace.UserSpaceID, TransferBytes: 1, }) } // 删除原本的文件块 blockChgs = append(blockChgs, &datamap.BlockChangeDeleted{ Index: 0, UserSpaceID: obj.Blocks[0].UserSpaceID, }) return &db.UpdatingObjectRedundancy{ ObjectID: obj.Object.ObjectID, FileHash: obj.Object.FileHash, Size: obj.Object.Size, Redundancy: red, Blocks: blocks, }, &datamap.BodyBlockTransfer{ ObjectID: obj.Object.ObjectID, PackageID: obj.Object.PackageID, BlockChanges: blockChgs, }, nil } func (t *ChangeRedundancy) repToEC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.ECRedundancy, uploadUserSpaces []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) { return t.noneToEC(ctx, obj, red, uploadUserSpaces) } func (t *ChangeRedundancy) ecToRep(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, srcRed *clitypes.ECRedundancy, tarRed *clitypes.RepRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) { var chosenBlocks []clitypes.GrouppedObjectBlock var chosenBlockIndexes []int var chosenBlockStg []clitypes.UserSpaceDetail for _, block := range obj.GroupBlocks() { if len(block.UserSpaceIDs) > 0 { // TODO 考虑选择最优的节点 stg, ok := ctx.allUserSpaces[block.UserSpaceIDs[0]] if !ok { continue } if stg.UserSpace.MasterHub == nil { continue } chosenBlocks = append(chosenBlocks, block) chosenBlockIndexes = append(chosenBlockIndexes, block.Index) chosenBlockStg = append(chosenBlockStg, *stg.UserSpace) } if len(chosenBlocks) == srcRed.K { break } } if len(chosenBlocks) < srcRed.K { return nil, nil, fmt.Errorf("no enough blocks to reconstruct the original file data") } // 如果选择的备份节点都是同一个,那么就只要上传一次 uploadStgs = lo.UniqBy(uploadStgs, func(item *userSpaceLoadInfo) clitypes.UserSpaceID { return item.UserSpace.UserSpace.UserSpaceID }) planBlder := exec.NewPlanBuilder() ft := ioswitch2.NewFromTo() ft.ECParam = srcRed for i, block := range chosenBlocks { ft.AddFrom(ioswitch2.NewFromShardstore(block.FileHash, *chosenBlockStg[i].MasterHub, chosenBlockStg[i], ioswitch2.ECStream(block.Index))) } for i := range uploadStgs { ft.AddTo(ioswitch2.NewToShardStoreWithRange(*uploadStgs[i].UserSpace.MasterHub, *uploadStgs[i].UserSpace, ioswitch2.RawStream(), fmt.Sprintf("%d", i), math2.NewRange(0, obj.Object.Size))) } err := parser.Parse(ft, planBlder) if err != nil { return nil, nil, fmt.Errorf("parsing plan: %w", err) } // TODO 添加依赖 execCtx := exec.NewExecContext() exec.SetValueByType(execCtx, ctx.ticktock.stgPool) ioRet, err := planBlder.Execute(execCtx).Wait(context.Background()) if err != nil { return nil, nil, fmt.Errorf("executing io plan: %w", err) } var blocks []clitypes.ObjectBlock for i := range uploadStgs { r := ioRet[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue) blocks = append(blocks, clitypes.ObjectBlock{ ObjectID: obj.Object.ObjectID, Index: 0, UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID, FileHash: r.Hash, Size: r.Size, }) } var evtSrcBlocks []datamap.Block var evtTargetBlocks []datamap.Block for i2, block := range chosenBlocks { evtSrcBlocks = append(evtSrcBlocks, datamap.Block{ BlockType: datamap.BlockTypeEC, Index: block.Index, UserSpaceID: chosenBlockStg[i2].UserSpace.UserSpaceID, }) } for _, stg := range uploadStgs { evtTargetBlocks = append(evtTargetBlocks, datamap.Block{ BlockType: datamap.BlockTypeRaw, Index: 0, UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID, }) } var evtBlockTrans []datamap.DataTransfer for _, stg := range uploadStgs { for i2 := range chosenBlocks { evtBlockTrans = append(evtBlockTrans, datamap.DataTransfer{ SourceUserSpaceID: chosenBlockStg[i2].UserSpace.UserSpaceID, TargetUserSpaceID: stg.UserSpace.UserSpace.UserSpaceID, TransferBytes: 1, }) } } var blockChgs []datamap.BlockChange blockChgs = append(blockChgs, &datamap.BlockChangeEnDecode{ SourceBlocks: evtSrcBlocks, TargetBlocks: evtTargetBlocks, DataTransfers: evtBlockTrans, }) for _, block := range obj.Blocks { blockChgs = append(blockChgs, &datamap.BlockChangeDeleted{ Index: block.Index, UserSpaceID: block.UserSpaceID, }) } return &db.UpdatingObjectRedundancy{ ObjectID: obj.Object.ObjectID, FileHash: obj.Object.FileHash, Size: obj.Object.Size, Redundancy: tarRed, Blocks: blocks, }, &datamap.BodyBlockTransfer{ ObjectID: obj.Object.ObjectID, PackageID: obj.Object.PackageID, BlockChanges: blockChgs, }, nil } func (t *ChangeRedundancy) ecToEC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, srcRed *clitypes.ECRedundancy, tarRed *clitypes.ECRedundancy, uploadUserSpaces []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) { grpBlocks := obj.GroupBlocks() var chosenBlocks []clitypes.GrouppedObjectBlock var chosenBlockStg []clitypes.UserSpaceDetail for _, block := range grpBlocks { if len(block.UserSpaceIDs) > 0 { stg, ok := ctx.allUserSpaces[block.UserSpaceIDs[0]] if !ok { continue } if stg.UserSpace.MasterHub == nil { continue } chosenBlocks = append(chosenBlocks, block) chosenBlockStg = append(chosenBlockStg, *stg.UserSpace) } if len(chosenBlocks) == srcRed.K { break } } if len(chosenBlocks) < srcRed.K { return nil, nil, fmt.Errorf("no enough blocks to reconstruct the original file data") } // 目前EC的参数都相同,所以可以不用重建出完整数据然后再分块,可以直接构建出目的节点需要的块 planBlder := exec.NewPlanBuilder() var evtSrcBlocks []datamap.Block var evtTargetBlocks []datamap.Block ft := ioswitch2.NewFromTo() ft.ECParam = srcRed for i, block := range chosenBlocks { ft.AddFrom(ioswitch2.NewFromShardstore(block.FileHash, *chosenBlockStg[i].MasterHub, chosenBlockStg[i], ioswitch2.ECStream(block.Index))) evtSrcBlocks = append(evtSrcBlocks, datamap.Block{ BlockType: datamap.BlockTypeEC, Index: block.Index, UserSpaceID: chosenBlockStg[i].UserSpace.UserSpaceID, }) } var newBlocks []clitypes.ObjectBlock shouldUpdateBlocks := false for i, stg := range uploadUserSpaces { newBlock := clitypes.ObjectBlock{ ObjectID: obj.Object.ObjectID, Index: i, UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID, } grp, ok := lo.Find(grpBlocks, func(grp clitypes.GrouppedObjectBlock) bool { return grp.Index == i }) // 如果新选中的节点已经记录在Block表中,那么就不需要任何变更 if ok && lo.Contains(grp.UserSpaceIDs, stg.UserSpace.UserSpace.UserSpaceID) { newBlock.FileHash = grp.FileHash newBlock.Size = grp.Size newBlocks = append(newBlocks, newBlock) continue } shouldUpdateBlocks = true // 否则就要重建出这个节点需要的块 // 输出只需要自己要保存的那一块 ft.AddTo(ioswitch2.NewToShardStore(*stg.UserSpace.MasterHub, *stg.UserSpace, ioswitch2.ECStream(i), fmt.Sprintf("%d", i))) evtTargetBlocks = append(evtTargetBlocks, datamap.Block{ BlockType: datamap.BlockTypeEC, Index: i, UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID, }) newBlocks = append(newBlocks, newBlock) } err := parser.Parse(ft, planBlder) if err != nil { return nil, nil, fmt.Errorf("parsing plan: %w", err) } // 如果没有任何Plan,Wait会直接返回成功 execCtx := exec.NewExecContext() exec.SetValueByType(execCtx, ctx.ticktock.stgPool) ret, err := planBlder.Execute(execCtx).Wait(context.Background()) if err != nil { return nil, nil, fmt.Errorf("executing io plan: %w", err) } if !shouldUpdateBlocks { return nil, nil, nil } for k, v := range ret { idx, err := strconv.ParseInt(k, 10, 64) if err != nil { return nil, nil, fmt.Errorf("parsing result key %s as index: %w", k, err) } r := v.(*ops2.ShardInfoValue) newBlocks[idx].FileHash = r.Hash newBlocks[idx].Size = r.Size } var evtBlockTrans []datamap.DataTransfer for _, src := range evtSrcBlocks { for _, tar := range evtTargetBlocks { evtBlockTrans = append(evtBlockTrans, datamap.DataTransfer{ SourceUserSpaceID: src.UserSpaceID, TargetUserSpaceID: tar.UserSpaceID, TransferBytes: 1, }) } } var blockChgs []datamap.BlockChange for _, block := range obj.Blocks { keep := lo.ContainsBy(newBlocks, func(newBlock clitypes.ObjectBlock) bool { return newBlock.Index == block.Index && newBlock.UserSpaceID == block.UserSpaceID }) if !keep { blockChgs = append(blockChgs, &datamap.BlockChangeDeleted{ Index: block.Index, UserSpaceID: block.UserSpaceID, }) } } blockChgs = append(blockChgs, &datamap.BlockChangeEnDecode{ SourceBlocks: evtSrcBlocks, TargetBlocks: evtTargetBlocks, DataTransfers: evtBlockTrans, }) return &db.UpdatingObjectRedundancy{ ObjectID: obj.Object.ObjectID, FileHash: obj.Object.FileHash, Size: obj.Object.Size, Redundancy: tarRed, Blocks: newBlocks, }, &datamap.BodyBlockTransfer{ ObjectID: obj.Object.ObjectID, PackageID: obj.Object.PackageID, BlockChanges: blockChgs, }, nil } func (t *ChangeRedundancy) lrcToLRC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, srcRed *clitypes.LRCRedundancy, tarRed *clitypes.LRCRedundancy, uploadUserSpaces []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) { blocksGrpByIndex := obj.GroupBlocks() var lostBlocks []int var lostBlockGrps []int canGroupReconstruct := true allBlockFlags := make([]bool, srcRed.N) for _, block := range blocksGrpByIndex { allBlockFlags[block.Index] = true } for i, ok := range allBlockFlags { grpID := srcRed.FindGroup(i) if !ok { if grpID == -1 { canGroupReconstruct = false break } if len(lostBlocks) > 0 && lostBlockGrps[len(lostBlockGrps)-1] == grpID { canGroupReconstruct = false break } lostBlocks = append(lostBlocks, i) lostBlockGrps = append(lostBlockGrps, grpID) } } // TODO 产生BlockTransfer事件 if canGroupReconstruct { // return t.groupReconstructLRC(obj, lostBlocks, lostBlockGrps, blocksGrpByIndex, srcRed, uploadUserSpaces) } return t.reconstructLRC(ctx, obj, blocksGrpByIndex, srcRed, uploadUserSpaces) } /* TODO2 修复这一块的代码 func (t *ChangeRedundancy) groupReconstructLRC(obj clitypes.ObjectDetail, lostBlocks []int, lostBlockGrps []int, grpedBlocks []clitypes.GrouppedObjectBlock, red *clitypes.LRCRedundancy, uploadUserSpaces []*UserSpaceLoadInfo) (*db.UpdatingObjectRedundancy, error) { grped := make(map[int]clitypes.GrouppedObjectBlock) for _, b := range grpedBlocks { grped[b.Index] = b } plans := exec.NewPlanBuilder() for i := 0; i < len(lostBlocks); i++ { var froms []ioswitchlrc.From grpEles := red.GetGroupElements(lostBlockGrps[i]) for _, ele := range grpEles { if ele == lostBlocks[i] { continue } froms = append(froms, ioswitchlrc.NewFromUserSpace(grped[ele].FileHash, nil, ele)) } err := lrcparser.ReconstructGroup(froms, []ioswitchlrc.To{ ioswitchlrc.NewToUserSpace(uploadUserSpaces[i].UserSpace, lostBlocks[i], fmt.Sprintf("%d", lostBlocks[i])), }, plans) if err != nil { return nil, fmt.Errorf("parsing plan: %w", err) } } fmt.Printf("plans: %v\n", plans) // 如果没有任何Plan,Wait会直接返回成功 // TODO 添加依赖 ret, err := plans.Execute(exec.NewExecContext()).Wait(context.TODO()) if err != nil { return nil, fmt.Errorf("executing io plan: %w", err) } var newBlocks []clitypes.ObjectBlock for _, i := range lostBlocks { newBlocks = append(newBlocks, clitypes.ObjectBlock{ ObjectID: obj.Object.ObjectID, Index: i, UserSpaceID: uploadUserSpaces[i].UserSpace.UserSpace.UserSpaceID, FileHash: ret[fmt.Sprintf("%d", i)].(*ops2.FileHashValue).Hash, }) } for _, b := range grpedBlocks { for _, hubID := range b.UserSpaceIDs { newBlocks = append(newBlocks, clitypes.ObjectBlock{ ObjectID: obj.Object.ObjectID, Index: b.Index, UserSpaceID: hubID, FileHash: b.FileHash, }) } } return &db.UpdatingObjectRedundancy{ ObjectID: obj.Object.ObjectID, Redundancy: red, Blocks: newBlocks, }, nil } */ func (t *ChangeRedundancy) reconstructLRC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, grpBlocks []clitypes.GrouppedObjectBlock, red *clitypes.LRCRedundancy, uploadUserSpaces []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) { var chosenBlocks []clitypes.GrouppedObjectBlock var chosenBlockStg []clitypes.UserSpaceDetail for _, block := range grpBlocks { if len(block.UserSpaceIDs) > 0 && block.Index < red.M() { stg, ok := ctx.allUserSpaces[block.UserSpaceIDs[0]] if !ok { continue } if stg.UserSpace.MasterHub == nil { continue } chosenBlocks = append(chosenBlocks, block) chosenBlockStg = append(chosenBlockStg, *stg.UserSpace) } if len(chosenBlocks) == red.K { break } } if len(chosenBlocks) < red.K { return nil, nil, fmt.Errorf("no enough blocks to reconstruct the original file data") } // 目前LRC的参数都相同,所以可以不用重建出完整数据然后再分块,可以直接构建出目的节点需要的块 planBlder := exec.NewPlanBuilder() var froms []ioswitchlrc.From var toes []ioswitchlrc.To var newBlocks []clitypes.ObjectBlock shouldUpdateBlocks := false for i, userspace := range uploadUserSpaces { newBlock := clitypes.ObjectBlock{ ObjectID: obj.Object.ObjectID, Index: i, UserSpaceID: userspace.UserSpace.UserSpace.UserSpaceID, } grp, ok := lo.Find(grpBlocks, func(grp clitypes.GrouppedObjectBlock) bool { return grp.Index == i }) // 如果新选中的节点已经记录在Block表中,那么就不需要任何变更 if ok && lo.Contains(grp.UserSpaceIDs, userspace.UserSpace.UserSpace.UserSpaceID) { newBlock.FileHash = grp.FileHash newBlock.Size = grp.Size newBlocks = append(newBlocks, newBlock) continue } shouldUpdateBlocks = true // 否则就要重建出这个节点需要的块 for i2, block := range chosenBlocks { froms = append(froms, ioswitchlrc.NewFromStorage(block.FileHash, *chosenBlockStg[i2].MasterHub, chosenBlockStg[i2], block.Index)) } // 输出只需要自己要保存的那一块 toes = append(toes, ioswitchlrc.NewToStorage(*userspace.UserSpace.MasterHub, *userspace.UserSpace, i, fmt.Sprintf("%d", i))) newBlocks = append(newBlocks, newBlock) } err := lrcparser.ReconstructAny(froms, toes, planBlder) if err != nil { return nil, nil, fmt.Errorf("parsing plan: %w", err) } fmt.Printf("plans: %v\n", planBlder) // 如果没有任何Plan,Wait会直接返回成功 execCtx := exec.NewExecContext() exec.SetValueByType(execCtx, ctx.ticktock.stgPool) ret, err := planBlder.Execute(execCtx).Wait(context.Background()) if err != nil { return nil, nil, fmt.Errorf("executing io plan: %w", err) } if !shouldUpdateBlocks { return nil, nil, nil } for k, v := range ret { idx, err := strconv.ParseInt(k, 10, 64) if err != nil { return nil, nil, fmt.Errorf("parsing result key %s as index: %w", k, err) } r := v.(*ops2.ShardInfoValue) newBlocks[idx].FileHash = r.Hash newBlocks[idx].Size = r.Size } // TODO 产生系统事件 return &db.UpdatingObjectRedundancy{ ObjectID: obj.Object.ObjectID, FileHash: obj.Object.FileHash, Size: obj.Object.Size, Redundancy: red, Blocks: newBlocks, }, nil, nil }