You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

upload_objects.go 7.0 kB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. package cmd
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "math"
  7. "math/rand"
  8. "time"
  9. "github.com/samber/lo"
  10. "gitlink.org.cn/cloudream/common/pkgs/distlock"
  11. "gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec"
  12. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  13. "gitlink.org.cn/cloudream/common/utils/sort2"
  14. stgglb "gitlink.org.cn/cloudream/storage/common/globals"
  15. stgmod "gitlink.org.cn/cloudream/storage/common/models"
  16. "gitlink.org.cn/cloudream/storage/common/pkgs/connectivity"
  17. "gitlink.org.cn/cloudream/storage/common/pkgs/distlock/reqbuilder"
  18. "gitlink.org.cn/cloudream/storage/common/pkgs/ioswitch2"
  19. "gitlink.org.cn/cloudream/storage/common/pkgs/ioswitch2/ops2"
  20. "gitlink.org.cn/cloudream/storage/common/pkgs/ioswitch2/parser"
  21. "gitlink.org.cn/cloudream/storage/common/pkgs/iterator"
  22. coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator"
  23. "gitlink.org.cn/cloudream/storage/common/pkgs/storage/mgr"
  24. )
  25. type UploadObjects struct {
  26. userID cdssdk.UserID
  27. packageID cdssdk.PackageID
  28. objectIter iterator.UploadingObjectIterator
  29. stgAffinity cdssdk.StorageID
  30. }
  31. type UploadObjectsResult struct {
  32. Objects []ObjectUploadResult
  33. }
  34. type ObjectUploadResult struct {
  35. Info *iterator.IterUploadingObject
  36. Error error
  37. Object cdssdk.Object
  38. }
  39. type UploadStorageInfo struct {
  40. Storage stgmod.StorageDetail
  41. Delay time.Duration
  42. IsSameLocation bool
  43. }
  44. type UploadObjectsContext struct {
  45. Distlock *distlock.Service
  46. Connectivity *connectivity.Collector
  47. StgMgr *mgr.Manager
  48. }
  49. func NewUploadObjects(userID cdssdk.UserID, packageID cdssdk.PackageID, objIter iterator.UploadingObjectIterator, stgAffinity cdssdk.StorageID) *UploadObjects {
  50. return &UploadObjects{
  51. userID: userID,
  52. packageID: packageID,
  53. objectIter: objIter,
  54. stgAffinity: stgAffinity,
  55. }
  56. }
  57. func (t *UploadObjects) Execute(ctx *UploadObjectsContext) (*UploadObjectsResult, error) {
  58. defer t.objectIter.Close()
  59. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  60. if err != nil {
  61. return nil, fmt.Errorf("new coordinator client: %w", err)
  62. }
  63. getUserStgsResp, err := coorCli.GetUserStorageDetails(coormq.ReqGetUserStorageDetails(t.userID))
  64. if err != nil {
  65. return nil, fmt.Errorf("getting user storages: %w", err)
  66. }
  67. cons := ctx.Connectivity.GetAll()
  68. var userStgs []UploadStorageInfo
  69. for _, stg := range getUserStgsResp.Storages {
  70. if stg.MasterHub == nil {
  71. continue
  72. }
  73. delay := time.Duration(math.MaxInt64)
  74. con, ok := cons[stg.MasterHub.HubID]
  75. if ok && con.Delay != nil {
  76. delay = *con.Delay
  77. }
  78. userStgs = append(userStgs, UploadStorageInfo{
  79. Storage: stg,
  80. Delay: delay,
  81. IsSameLocation: stg.MasterHub.LocationID == stgglb.Local.LocationID,
  82. })
  83. }
  84. if len(userStgs) == 0 {
  85. return nil, fmt.Errorf("user no available storages")
  86. }
  87. // 给上传节点的IPFS加锁
  88. lockBlder := reqbuilder.NewBuilder()
  89. for _, us := range userStgs {
  90. lockBlder.Shard().Buzy(us.Storage.Storage.StorageID)
  91. }
  92. // TODO 考虑加Object的Create锁
  93. // 防止上传的副本被清除
  94. ipfsMutex, err := lockBlder.MutexLock(ctx.Distlock)
  95. if err != nil {
  96. return nil, fmt.Errorf("acquire locks failed, err: %w", err)
  97. }
  98. defer ipfsMutex.Unlock()
  99. rets, err := uploadAndUpdatePackage(ctx, t.packageID, t.objectIter, userStgs, t.stgAffinity)
  100. if err != nil {
  101. return nil, err
  102. }
  103. return &UploadObjectsResult{
  104. Objects: rets,
  105. }, nil
  106. }
  107. // chooseUploadStorage 选择一个上传文件的节点
  108. // 1. 选择设置了亲和性的节点
  109. // 2. 从与当前客户端相同地域的节点中随机选一个
  110. // 3. 没有的话从所有节点选择延迟最低的节点
  111. func chooseUploadStorage(storages []UploadStorageInfo, stgAffinity cdssdk.StorageID) UploadStorageInfo {
  112. if stgAffinity > 0 {
  113. aff, ok := lo.Find(storages, func(storage UploadStorageInfo) bool { return storage.Storage.Storage.StorageID == stgAffinity })
  114. if ok {
  115. return aff
  116. }
  117. }
  118. sameLocationStorages := lo.Filter(storages, func(e UploadStorageInfo, i int) bool { return e.IsSameLocation })
  119. if len(sameLocationStorages) > 0 {
  120. return sameLocationStorages[rand.Intn(len(sameLocationStorages))]
  121. }
  122. // 选择延迟最低的节点
  123. storages = sort2.Sort(storages, func(e1, e2 UploadStorageInfo) int { return sort2.Cmp(e1.Delay, e2.Delay) })
  124. return storages[0]
  125. }
  126. func uploadAndUpdatePackage(ctx *UploadObjectsContext, packageID cdssdk.PackageID, objectIter iterator.UploadingObjectIterator, userStorages []UploadStorageInfo, stgAffinity cdssdk.StorageID) ([]ObjectUploadResult, error) {
  127. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  128. if err != nil {
  129. return nil, fmt.Errorf("new coordinator client: %w", err)
  130. }
  131. defer stgglb.CoordinatorMQPool.Release(coorCli)
  132. // 为所有文件选择相同的上传节点
  133. uploadStorage := chooseUploadStorage(userStorages, stgAffinity)
  134. var uploadRets []ObjectUploadResult
  135. //上传文件夹
  136. var adds []coormq.AddObjectEntry
  137. for {
  138. objInfo, err := objectIter.MoveNext()
  139. if err == iterator.ErrNoMoreItem {
  140. break
  141. }
  142. if err != nil {
  143. return nil, fmt.Errorf("reading object: %w", err)
  144. }
  145. err = func() error {
  146. defer objInfo.File.Close()
  147. uploadTime := time.Now()
  148. fileHash, err := uploadFile(ctx, objInfo.File, uploadStorage)
  149. if err != nil {
  150. return fmt.Errorf("uploading file: %w", err)
  151. }
  152. uploadRets = append(uploadRets, ObjectUploadResult{
  153. Info: objInfo,
  154. Error: err,
  155. })
  156. adds = append(adds, coormq.NewAddObjectEntry(objInfo.Path, objInfo.Size, fileHash, uploadTime, uploadStorage.Storage.Storage.StorageID))
  157. return nil
  158. }()
  159. if err != nil {
  160. return nil, err
  161. }
  162. }
  163. updateResp, err := coorCli.UpdatePackage(coormq.NewUpdatePackage(packageID, adds, nil))
  164. if err != nil {
  165. return nil, fmt.Errorf("updating package: %w", err)
  166. }
  167. updatedObjs := make(map[string]*cdssdk.Object)
  168. for _, obj := range updateResp.Added {
  169. o := obj
  170. updatedObjs[obj.Path] = &o
  171. }
  172. for i := range uploadRets {
  173. obj := updatedObjs[uploadRets[i].Info.Path]
  174. if obj == nil {
  175. uploadRets[i].Error = fmt.Errorf("object %s not found in package", uploadRets[i].Info.Path)
  176. continue
  177. }
  178. uploadRets[i].Object = *obj
  179. }
  180. return uploadRets, nil
  181. }
  182. func uploadFile(ctx *UploadObjectsContext, file io.Reader, uploadStg UploadStorageInfo) (cdssdk.FileHash, error) {
  183. ft := ioswitch2.NewFromTo()
  184. fromExec, hd := ioswitch2.NewFromDriver(-1)
  185. ft.AddFrom(fromExec).AddTo(ioswitch2.NewToShardStore(*uploadStg.Storage.MasterHub, uploadStg.Storage.Storage, -1, "fileHash"))
  186. parser := parser.NewParser(cdssdk.DefaultECRedundancy)
  187. plans := exec.NewPlanBuilder()
  188. err := parser.Parse(ft, plans)
  189. if err != nil {
  190. return "", fmt.Errorf("parsing plan: %w", err)
  191. }
  192. exeCtx := exec.NewExecContext()
  193. exec.SetValueByType(exeCtx, ctx.StgMgr)
  194. exec := plans.Execute(exeCtx)
  195. exec.BeginWrite(io.NopCloser(file), hd)
  196. ret, err := exec.Wait(context.TODO())
  197. if err != nil {
  198. return "", err
  199. }
  200. return ret["fileHash"].(*ops2.FileHashValue).Hash, nil
  201. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。