package cmd import ( "fmt" "github.com/samber/lo" mysort "gitlink.org.cn/cloudream/common/utils/sort" "gitlink.org.cn/cloudream/storage/common/pkgs/distlock/reqbuilder" stgglb "gitlink.org.cn/cloudream/storage/common/globals" "gitlink.org.cn/cloudream/storage/common/pkgs/db/model" "gitlink.org.cn/cloudream/storage/common/pkgs/iterator" coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator" ) type UpdateRepPackage struct { userID int64 packageID int64 objectIter iterator.UploadingObjectIterator } type UpdateNodeInfo struct { UploadNodeInfo HasOldObject bool } type UpdateRepPackageResult struct { ObjectResults []RepObjectUploadResult } func NewUpdateRepPackage(userID int64, packageID int64, objectIter iterator.UploadingObjectIterator) *UpdateRepPackage { return &UpdateRepPackage{ userID: userID, packageID: packageID, objectIter: objectIter, } } func (t *UpdateRepPackage) Execute(ctx *UpdatePackageContext) (*UpdateRepPackageResult, error) { defer t.objectIter.Close() coorCli, err := stgglb.CoordinatorMQPool.Acquire() if err != nil { return nil, fmt.Errorf("new coordinator client: %w", err) } reqBlder := reqbuilder.NewBuilder() // 如果本地的IPFS也是存储系统的一个节点,那么从本地上传时,需要加锁 if stgglb.Local.NodeID != nil { reqBlder.IPFS().CreateAnyRep(*stgglb.Local.NodeID) } mutex, err := reqBlder. Metadata(). // 用于查询可用的上传节点 Node().ReadAny(). // 用于创建包信息 Package().WriteOne(t.packageID). // 用于创建包中的文件的信息 Object().CreateAny(). // 用于设置EC配置 ObjectBlock().CreateAny(). // 用于创建Cache记录 Cache().CreateAny(). MutexLock(ctx.Distlock) if err != nil { return nil, fmt.Errorf("acquire locks failed, err: %w", err) } defer mutex.Unlock() getUserNodesResp, err := coorCli.GetUserNodes(coormq.NewGetUserNodes(t.userID)) if err != nil { return nil, fmt.Errorf("getting user nodes: %w", err) } findCliLocResp, err := coorCli.FindClientLocation(coormq.NewFindClientLocation(stgglb.Local.ExternalIP)) if err != nil { return nil, fmt.Errorf("finding client location: %w", err) } nodeInfos := lo.Map(getUserNodesResp.Nodes, func(node model.Node, index int) UpdateNodeInfo { return UpdateNodeInfo{ UploadNodeInfo: UploadNodeInfo{ Node: node, IsSameLocation: node.LocationID == findCliLocResp.Location.LocationID, }, } }) // 上传文件的方式优先级: // 1. 本地IPFS // 2. 包含了旧文件,且与客户端在同地域的节点 // 3. 不在同地域,但包含了旧文件的节点 // 4. 同地域节点 // TODO 需要考虑在多文件的情况下的规则 uploadNode := t.chooseUploadNode(nodeInfos) // 防止上传的副本被清除 ipfsMutex, err := reqbuilder.NewBuilder(). IPFS().CreateAnyRep(uploadNode.Node.NodeID). MutexLock(ctx.Distlock) if err != nil { return nil, fmt.Errorf("acquire locks failed, err: %w", err) } defer ipfsMutex.Unlock() rets, err := uploadAndUpdateRepPackage(t.packageID, t.objectIter, uploadNode.UploadNodeInfo) if err != nil { return nil, err } return &UpdateRepPackageResult{ ObjectResults: rets, }, nil } // chooseUploadNode 选择一个上传文件的节点 // 1. 从与当前客户端相同地域的节点中随机选一个 // 2. 没有用的话从所有节点中随机选一个 func (t *UpdateRepPackage) chooseUploadNode(nodes []UpdateNodeInfo) UpdateNodeInfo { mysort.Sort(nodes, func(left, right UpdateNodeInfo) int { v := -mysort.CmpBool(left.HasOldObject, right.HasOldObject) if v != 0 { return v } return -mysort.CmpBool(left.IsSameLocation, right.IsSameLocation) }) return nodes[0] }