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.

storage_load_package.go 11 kB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. package task
  2. import (
  3. "fmt"
  4. "io"
  5. "math"
  6. "os"
  7. "path/filepath"
  8. "time"
  9. "github.com/samber/lo"
  10. "gitlink.org.cn/cloudream/common/pkgs/bitmap"
  11. "gitlink.org.cn/cloudream/common/pkgs/ipfs"
  12. "gitlink.org.cn/cloudream/common/pkgs/logger"
  13. "gitlink.org.cn/cloudream/common/pkgs/task"
  14. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  15. "gitlink.org.cn/cloudream/common/utils/io2"
  16. "gitlink.org.cn/cloudream/common/utils/reflect2"
  17. "gitlink.org.cn/cloudream/common/utils/sort2"
  18. "gitlink.org.cn/cloudream/storage/common/consts"
  19. stgglb "gitlink.org.cn/cloudream/storage/common/globals"
  20. stgmod "gitlink.org.cn/cloudream/storage/common/models"
  21. "gitlink.org.cn/cloudream/storage/common/pkgs/distlock/reqbuilder"
  22. "gitlink.org.cn/cloudream/storage/common/pkgs/ec"
  23. coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator"
  24. "gitlink.org.cn/cloudream/storage/common/utils"
  25. )
  26. type StorageLoadPackage struct {
  27. PackagePath string
  28. LocalBase string
  29. RemoteBase string
  30. userID cdssdk.UserID
  31. packageID cdssdk.PackageID
  32. storageID cdssdk.StorageID
  33. pinnedBlocks []stgmod.ObjectBlock
  34. }
  35. func NewStorageLoadPackage(userID cdssdk.UserID, packageID cdssdk.PackageID, storageID cdssdk.StorageID) *StorageLoadPackage {
  36. return &StorageLoadPackage{
  37. userID: userID,
  38. packageID: packageID,
  39. storageID: storageID,
  40. }
  41. }
  42. func (t *StorageLoadPackage) Execute(task *task.Task[TaskContext], ctx TaskContext, complete CompleteFn) {
  43. startTime := time.Now()
  44. log := logger.WithType[StorageLoadPackage]("Task")
  45. log.WithField("TaskID", task.ID()).
  46. Infof("begin to load package %v to %v", t.packageID, t.storageID)
  47. err := t.do(task, ctx)
  48. if err == nil {
  49. log.WithField("TaskID", task.ID()).
  50. Infof("loading success, cost: %v", time.Since(startTime))
  51. } else {
  52. log.WithField("TaskID", task.ID()).
  53. Warnf("loading package: %v, cost: %v", err, time.Since(startTime))
  54. }
  55. complete(err, CompleteOption{
  56. RemovingDelay: time.Minute,
  57. })
  58. }
  59. func (t *StorageLoadPackage) do(task *task.Task[TaskContext], ctx TaskContext) error {
  60. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  61. if err != nil {
  62. return fmt.Errorf("new coordinator client: %w", err)
  63. }
  64. defer stgglb.CoordinatorMQPool.Release(coorCli)
  65. ipfsCli, err := stgglb.IPFSPool.Acquire()
  66. if err != nil {
  67. return fmt.Errorf("new IPFS client: %w", err)
  68. }
  69. defer stgglb.IPFSPool.Release(ipfsCli)
  70. getStgResp, err := coorCli.GetStorage(coormq.ReqGetStorage(t.userID, t.storageID))
  71. if err != nil {
  72. return fmt.Errorf("request to coordinator: %w", err)
  73. }
  74. t.PackagePath = utils.MakeLoadedPackagePath(t.userID, t.packageID)
  75. fullLocalPath := filepath.Join(getStgResp.Storage.LocalBase, t.PackagePath)
  76. if err = os.MkdirAll(fullLocalPath, 0755); err != nil {
  77. return fmt.Errorf("creating output directory: %w", err)
  78. }
  79. getObjectDetails, err := coorCli.GetPackageObjectDetails(coormq.ReqGetPackageObjectDetails(t.packageID))
  80. if err != nil {
  81. return fmt.Errorf("getting package object details: %w", err)
  82. }
  83. mutex, err := reqbuilder.NewBuilder().
  84. // 提前占位
  85. Metadata().StoragePackage().CreateOne(t.userID, t.storageID, t.packageID).
  86. // 保护在storage目录中下载的文件
  87. Storage().Buzy(t.storageID).
  88. // 保护下载文件时同时保存到IPFS的文件
  89. IPFS().Buzy(getStgResp.Storage.NodeID).
  90. MutexLock(ctx.distlock)
  91. if err != nil {
  92. return fmt.Errorf("acquire locks failed, err: %w", err)
  93. }
  94. defer mutex.Unlock()
  95. for _, obj := range getObjectDetails.Objects {
  96. err := t.downloadOne(coorCli, ipfsCli, fullLocalPath, obj)
  97. if err != nil {
  98. return err
  99. }
  100. ctx.accessStat.AddAccessCounter(obj.Object.ObjectID, t.packageID, *stgglb.Local.NodeID, 1)
  101. }
  102. _, err = coorCli.StoragePackageLoaded(coormq.NewStoragePackageLoaded(t.userID, t.storageID, t.packageID, t.pinnedBlocks))
  103. if err != nil {
  104. return fmt.Errorf("loading package to storage: %w", err)
  105. }
  106. // TODO 要防止下载的临时文件被删除
  107. return err
  108. }
  109. func (t *StorageLoadPackage) downloadOne(coorCli *coormq.Client, ipfsCli *ipfs.PoolClient, dir string, obj stgmod.ObjectDetail) error {
  110. var file io.ReadCloser
  111. switch red := obj.Object.Redundancy.(type) {
  112. case *cdssdk.NoneRedundancy:
  113. reader, err := t.downloadNoneOrRepObject(ipfsCli, obj)
  114. if err != nil {
  115. return fmt.Errorf("downloading object: %w", err)
  116. }
  117. file = reader
  118. case *cdssdk.RepRedundancy:
  119. reader, err := t.downloadNoneOrRepObject(ipfsCli, obj)
  120. if err != nil {
  121. return fmt.Errorf("downloading rep object: %w", err)
  122. }
  123. file = reader
  124. case *cdssdk.ECRedundancy:
  125. reader, pinnedBlocks, err := t.downloadECObject(coorCli, ipfsCli, obj, red)
  126. if err != nil {
  127. return fmt.Errorf("downloading ec object: %w", err)
  128. }
  129. file = reader
  130. t.pinnedBlocks = append(t.pinnedBlocks, pinnedBlocks...)
  131. default:
  132. return fmt.Errorf("unknow redundancy type: %v", reflect2.TypeOfValue(obj.Object.Redundancy))
  133. }
  134. defer file.Close()
  135. fullPath := filepath.Join(dir, obj.Object.Path)
  136. lastDirPath := filepath.Dir(fullPath)
  137. if err := os.MkdirAll(lastDirPath, 0755); err != nil {
  138. return fmt.Errorf("creating object last dir: %w", err)
  139. }
  140. outputFile, err := os.Create(fullPath)
  141. if err != nil {
  142. return fmt.Errorf("creating object file: %w", err)
  143. }
  144. defer outputFile.Close()
  145. if _, err := io.Copy(outputFile, file); err != nil {
  146. return fmt.Errorf("writting object to file: %w", err)
  147. }
  148. return nil
  149. }
  150. func (t *StorageLoadPackage) downloadNoneOrRepObject(ipfsCli *ipfs.PoolClient, obj stgmod.ObjectDetail) (io.ReadCloser, error) {
  151. if len(obj.Blocks) == 0 && len(obj.PinnedAt) == 0 {
  152. return nil, fmt.Errorf("no node has this object")
  153. }
  154. // 不管实际有没有成功
  155. ipfsCli.Pin(obj.Object.FileHash)
  156. file, err := ipfsCli.OpenRead(obj.Object.FileHash)
  157. if err != nil {
  158. return nil, err
  159. }
  160. return file, nil
  161. }
  162. func (t *StorageLoadPackage) downloadECObject(coorCli *coormq.Client, ipfsCli *ipfs.PoolClient, obj stgmod.ObjectDetail, ecRed *cdssdk.ECRedundancy) (io.ReadCloser, []stgmod.ObjectBlock, error) {
  163. allNodes, err := t.sortDownloadNodes(coorCli, obj)
  164. if err != nil {
  165. return nil, nil, err
  166. }
  167. bsc, blocks := t.getMinReadingBlockSolution(allNodes, ecRed.K)
  168. osc, _ := t.getMinReadingObjectSolution(allNodes, ecRed.K)
  169. if bsc < osc {
  170. var fileStrs []io.ReadCloser
  171. rs, err := ec.NewStreamRs(ecRed.K, ecRed.N, ecRed.ChunkSize)
  172. if err != nil {
  173. return nil, nil, fmt.Errorf("new rs: %w", err)
  174. }
  175. for i := range blocks {
  176. // 不管实际有没有成功
  177. ipfsCli.Pin(blocks[i].Block.FileHash)
  178. str, err := ipfsCli.OpenRead(blocks[i].Block.FileHash)
  179. if err != nil {
  180. for i -= 1; i >= 0; i-- {
  181. fileStrs[i].Close()
  182. }
  183. return nil, nil, fmt.Errorf("donwloading file: %w", err)
  184. }
  185. fileStrs = append(fileStrs, str)
  186. }
  187. fileReaders, filesCloser := io2.ToReaders(fileStrs)
  188. var indexes []int
  189. var pinnedBlocks []stgmod.ObjectBlock
  190. for _, b := range blocks {
  191. indexes = append(indexes, b.Block.Index)
  192. pinnedBlocks = append(pinnedBlocks, stgmod.ObjectBlock{
  193. ObjectID: b.Block.ObjectID,
  194. Index: b.Block.Index,
  195. NodeID: *stgglb.Local.NodeID,
  196. FileHash: b.Block.FileHash,
  197. })
  198. }
  199. outputs, outputsCloser := io2.ToReaders(rs.ReconstructData(fileReaders, indexes))
  200. return io2.AfterReadClosed(io2.Length(io2.ChunkedJoin(outputs, int(ecRed.ChunkSize)), obj.Object.Size), func(c io.ReadCloser) {
  201. filesCloser()
  202. outputsCloser()
  203. }), pinnedBlocks, nil
  204. }
  205. // bsc >= osc,如果osc是MaxFloat64,那么bsc也一定是,也就意味着没有足够块来恢复文件
  206. if osc == math.MaxFloat64 {
  207. return nil, nil, fmt.Errorf("no enough blocks to reconstruct the file, want %d, get only %d", ecRed.K, len(blocks))
  208. }
  209. // 如果是直接读取的文件,那么就不需要Pin文件块
  210. str, err := ipfsCli.OpenRead(obj.Object.FileHash)
  211. return str, nil, err
  212. }
  213. type downloadNodeInfo struct {
  214. Node cdssdk.Node
  215. ObjectPinned bool
  216. Blocks []stgmod.ObjectBlock
  217. Distance float64
  218. }
  219. func (t *StorageLoadPackage) sortDownloadNodes(coorCli *coormq.Client, obj stgmod.ObjectDetail) ([]*downloadNodeInfo, error) {
  220. var nodeIDs []cdssdk.NodeID
  221. for _, id := range obj.PinnedAt {
  222. if !lo.Contains(nodeIDs, id) {
  223. nodeIDs = append(nodeIDs, id)
  224. }
  225. }
  226. for _, b := range obj.Blocks {
  227. if !lo.Contains(nodeIDs, b.NodeID) {
  228. nodeIDs = append(nodeIDs, b.NodeID)
  229. }
  230. }
  231. getNodes, err := coorCli.GetNodes(coormq.NewGetNodes(nodeIDs))
  232. if err != nil {
  233. return nil, fmt.Errorf("getting nodes: %w", err)
  234. }
  235. downloadNodeMap := make(map[cdssdk.NodeID]*downloadNodeInfo)
  236. for _, id := range obj.PinnedAt {
  237. node, ok := downloadNodeMap[id]
  238. if !ok {
  239. mod := *getNodes.GetNode(id)
  240. node = &downloadNodeInfo{
  241. Node: mod,
  242. ObjectPinned: true,
  243. Distance: t.getNodeDistance(mod),
  244. }
  245. downloadNodeMap[id] = node
  246. }
  247. node.ObjectPinned = true
  248. }
  249. for _, b := range obj.Blocks {
  250. node, ok := downloadNodeMap[b.NodeID]
  251. if !ok {
  252. mod := *getNodes.GetNode(b.NodeID)
  253. node = &downloadNodeInfo{
  254. Node: mod,
  255. Distance: t.getNodeDistance(mod),
  256. }
  257. downloadNodeMap[b.NodeID] = node
  258. }
  259. node.Blocks = append(node.Blocks, b)
  260. }
  261. return sort2.Sort(lo.Values(downloadNodeMap), func(left, right *downloadNodeInfo) int {
  262. return sort2.Cmp(left.Distance, right.Distance)
  263. }), nil
  264. }
  265. type downloadBlock struct {
  266. Node cdssdk.Node
  267. Block stgmod.ObjectBlock
  268. }
  269. func (t *StorageLoadPackage) getMinReadingBlockSolution(sortedNodes []*downloadNodeInfo, k int) (float64, []downloadBlock) {
  270. gotBlocksMap := bitmap.Bitmap64(0)
  271. var gotBlocks []downloadBlock
  272. dist := float64(0.0)
  273. for _, n := range sortedNodes {
  274. for _, b := range n.Blocks {
  275. if !gotBlocksMap.Get(b.Index) {
  276. gotBlocks = append(gotBlocks, downloadBlock{
  277. Node: n.Node,
  278. Block: b,
  279. })
  280. gotBlocksMap.Set(b.Index, true)
  281. dist += n.Distance
  282. }
  283. if len(gotBlocks) >= k {
  284. return dist, gotBlocks
  285. }
  286. }
  287. }
  288. return math.MaxFloat64, gotBlocks
  289. }
  290. func (t *StorageLoadPackage) getMinReadingObjectSolution(sortedNodes []*downloadNodeInfo, k int) (float64, *cdssdk.Node) {
  291. dist := math.MaxFloat64
  292. var downloadNode *cdssdk.Node
  293. for _, n := range sortedNodes {
  294. if n.ObjectPinned && float64(k)*n.Distance < dist {
  295. dist = float64(k) * n.Distance
  296. node := n.Node
  297. downloadNode = &node
  298. }
  299. }
  300. return dist, downloadNode
  301. }
  302. func (t *StorageLoadPackage) getNodeDistance(node cdssdk.Node) float64 {
  303. if stgglb.Local.NodeID != nil {
  304. if node.NodeID == *stgglb.Local.NodeID {
  305. return consts.NodeDistanceSameNode
  306. }
  307. }
  308. if node.LocationID == stgglb.Local.LocationID {
  309. return consts.NodeDistanceSameLocation
  310. }
  311. return consts.NodeDistanceOther
  312. }

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