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.

check_package_redundancy.go 20 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. package event
  2. import (
  3. "fmt"
  4. "strconv"
  5. "time"
  6. "github.com/samber/lo"
  7. "gitlink.org.cn/cloudream/common/pkgs/logger"
  8. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  9. "gitlink.org.cn/cloudream/common/utils/sort2"
  10. stgglb "gitlink.org.cn/cloudream/storage/common/globals"
  11. stgmod "gitlink.org.cn/cloudream/storage/common/models"
  12. "gitlink.org.cn/cloudream/storage/common/pkgs/distlock/reqbuilder"
  13. "gitlink.org.cn/cloudream/storage/common/pkgs/ioswitch/plans"
  14. agtmq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/agent"
  15. coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator"
  16. scevt "gitlink.org.cn/cloudream/storage/common/pkgs/mq/scanner/event"
  17. "gitlink.org.cn/cloudream/storage/scanner/internal/config"
  18. )
  19. const (
  20. monthHours = 30 * 24
  21. yearHours = 365 * 24
  22. )
  23. type CheckPackageRedundancy struct {
  24. *scevt.CheckPackageRedundancy
  25. }
  26. func NewCheckPackageRedundancy(evt *scevt.CheckPackageRedundancy) *CheckPackageRedundancy {
  27. return &CheckPackageRedundancy{
  28. CheckPackageRedundancy: evt,
  29. }
  30. }
  31. type NodeLoadInfo struct {
  32. Node cdssdk.Node
  33. LoadsRecentMonth int
  34. LoadsRecentYear int
  35. }
  36. func (t *CheckPackageRedundancy) TryMerge(other Event) bool {
  37. event, ok := other.(*CheckPackageRedundancy)
  38. if !ok {
  39. return false
  40. }
  41. return event.PackageID == t.PackageID
  42. }
  43. func (t *CheckPackageRedundancy) Execute(execCtx ExecuteContext) {
  44. log := logger.WithType[CheckPackageRedundancy]("Event")
  45. log.Debugf("begin with %v", logger.FormatStruct(t.CheckPackageRedundancy))
  46. defer log.Debugf("end")
  47. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  48. if err != nil {
  49. log.Warnf("new coordinator client: %s", err.Error())
  50. return
  51. }
  52. defer stgglb.CoordinatorMQPool.Release(coorCli)
  53. getObjs, err := coorCli.GetPackageObjectDetails(coormq.NewGetPackageObjectDetails(t.PackageID))
  54. if err != nil {
  55. log.Warnf("getting package objects: %s", err.Error())
  56. return
  57. }
  58. getLogs, err := coorCli.GetPackageLoadLogDetails(coormq.ReqGetPackageLoadLogDetails(t.PackageID))
  59. if err != nil {
  60. log.Warnf("getting package load log details: %s", err.Error())
  61. return
  62. }
  63. // TODO UserID
  64. getNodes, err := coorCli.GetUserNodes(coormq.NewGetUserNodes(0))
  65. if err != nil {
  66. log.Warnf("getting all nodes: %s", err.Error())
  67. return
  68. }
  69. if len(getNodes.Nodes) == 0 {
  70. log.Warnf("no available nodes")
  71. return
  72. }
  73. allNodes := make(map[cdssdk.NodeID]*NodeLoadInfo)
  74. for _, node := range getNodes.Nodes {
  75. allNodes[node.NodeID] = &NodeLoadInfo{
  76. Node: node,
  77. }
  78. }
  79. for _, log := range getLogs.Logs {
  80. info, ok := allNodes[log.Storage.NodeID]
  81. if !ok {
  82. continue
  83. }
  84. sinceNow := time.Since(log.CreateTime)
  85. if sinceNow.Hours() < monthHours {
  86. info.LoadsRecentMonth++
  87. } else if sinceNow.Hours() < yearHours {
  88. info.LoadsRecentYear++
  89. }
  90. }
  91. var changedObjects []coormq.ChangeObjectRedundancyEntry
  92. defRep := cdssdk.DefaultRepRedundancy
  93. defEC := cdssdk.DefaultECRedundancy
  94. // TODO 目前rep的备份数量固定为2,所以这里直接选出两个节点
  95. mostBlockNodeIDs := t.summaryRepObjectBlockNodes(getObjs.Objects, 2)
  96. newRepNodes := t.chooseNewNodesForRep(&defRep, allNodes)
  97. rechoosedRepNodes := t.rechooseNodesForRep(mostBlockNodeIDs, &defRep, allNodes)
  98. newECNodes := t.chooseNewNodesForEC(&defEC, allNodes)
  99. // 加锁
  100. builder := reqbuilder.NewBuilder()
  101. for _, node := range newRepNodes {
  102. builder.IPFS().Buzy(node.Node.NodeID)
  103. }
  104. for _, node := range newECNodes {
  105. builder.IPFS().Buzy(node.Node.NodeID)
  106. }
  107. mutex, err := builder.MutexLock(execCtx.Args.DistLock)
  108. if err != nil {
  109. log.Warnf("acquiring dist lock: %s", err.Error())
  110. return
  111. }
  112. defer mutex.Unlock()
  113. for _, obj := range getObjs.Objects {
  114. var entry *coormq.ChangeObjectRedundancyEntry
  115. var err error
  116. shouldUseEC := obj.Object.Size > config.Cfg().ECFileSizeThreshold
  117. switch red := obj.Object.Redundancy.(type) {
  118. case *cdssdk.NoneRedundancy:
  119. if shouldUseEC {
  120. log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> ec")
  121. entry, err = t.noneToEC(obj, &defEC, newECNodes)
  122. } else {
  123. log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> rep")
  124. entry, err = t.noneToRep(obj, &defRep, newRepNodes)
  125. }
  126. case *cdssdk.RepRedundancy:
  127. if shouldUseEC {
  128. log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: rep -> ec")
  129. entry, err = t.repToEC(obj, &defEC, newECNodes)
  130. } else {
  131. entry, err = t.repToRep(obj, &defRep, rechoosedRepNodes)
  132. }
  133. case *cdssdk.ECRedundancy:
  134. if shouldUseEC {
  135. uploadNodes := t.rechooseNodesForEC(obj, red, allNodes)
  136. entry, err = t.ecToEC(obj, red, &defEC, uploadNodes)
  137. } else {
  138. log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: ec -> rep")
  139. entry, err = t.ecToRep(obj, red, &defRep, newRepNodes)
  140. }
  141. }
  142. if entry != nil {
  143. changedObjects = append(changedObjects, *entry)
  144. }
  145. if err != nil {
  146. log.WithField("ObjectID", obj.Object.ObjectID).Warnf("%s, its redundancy wont be changed", err.Error())
  147. }
  148. }
  149. if len(changedObjects) == 0 {
  150. return
  151. }
  152. _, err = coorCli.ChangeObjectRedundancy(coormq.ReqChangeObjectRedundancy(changedObjects))
  153. if err != nil {
  154. log.Warnf("requesting to change object redundancy: %s", err.Error())
  155. return
  156. }
  157. }
  158. // 统计每个对象块所在的节点,选出块最多的不超过nodeCnt个节点
  159. func (t *CheckPackageRedundancy) summaryRepObjectBlockNodes(objs []stgmod.ObjectDetail, nodeCnt int) []cdssdk.NodeID {
  160. type nodeBlocks struct {
  161. NodeID cdssdk.NodeID
  162. Count int
  163. }
  164. nodeBlocksMap := make(map[cdssdk.NodeID]*nodeBlocks)
  165. for _, obj := range objs {
  166. shouldUseEC := obj.Object.Size > config.Cfg().ECFileSizeThreshold
  167. if _, ok := obj.Object.Redundancy.(*cdssdk.RepRedundancy); ok && !shouldUseEC {
  168. for _, block := range obj.Blocks {
  169. if _, ok := nodeBlocksMap[block.NodeID]; !ok {
  170. nodeBlocksMap[block.NodeID] = &nodeBlocks{
  171. NodeID: block.NodeID,
  172. Count: 0,
  173. }
  174. }
  175. nodeBlocksMap[block.NodeID].Count++
  176. }
  177. }
  178. }
  179. nodes := lo.Values(nodeBlocksMap)
  180. sort2.Sort(nodes, func(left *nodeBlocks, right *nodeBlocks) int {
  181. return right.Count - left.Count
  182. })
  183. ids := lo.Map(nodes, func(item *nodeBlocks, idx int) cdssdk.NodeID { return item.NodeID })
  184. if len(ids) > nodeCnt {
  185. ids = ids[:nodeCnt]
  186. }
  187. return ids
  188. }
  189. func (t *CheckPackageRedundancy) chooseNewNodesForRep(red *cdssdk.RepRedundancy, allNodes map[cdssdk.NodeID]*NodeLoadInfo) []*NodeLoadInfo {
  190. sortedNodes := sort2.Sort(lo.Values(allNodes), func(left *NodeLoadInfo, right *NodeLoadInfo) int {
  191. dm := right.LoadsRecentMonth - left.LoadsRecentMonth
  192. if dm != 0 {
  193. return dm
  194. }
  195. return right.LoadsRecentYear - left.LoadsRecentYear
  196. })
  197. return t.chooseSoManyNodes(red.RepCount, sortedNodes)
  198. }
  199. func (t *CheckPackageRedundancy) chooseNewNodesForEC(red *cdssdk.ECRedundancy, allNodes map[cdssdk.NodeID]*NodeLoadInfo) []*NodeLoadInfo {
  200. sortedNodes := sort2.Sort(lo.Values(allNodes), func(left *NodeLoadInfo, right *NodeLoadInfo) int {
  201. dm := right.LoadsRecentMonth - left.LoadsRecentMonth
  202. if dm != 0 {
  203. return dm
  204. }
  205. return right.LoadsRecentYear - left.LoadsRecentYear
  206. })
  207. return t.chooseSoManyNodes(red.N, sortedNodes)
  208. }
  209. func (t *CheckPackageRedundancy) rechooseNodesForRep(mostBlockNodeIDs []cdssdk.NodeID, red *cdssdk.RepRedundancy, allNodes map[cdssdk.NodeID]*NodeLoadInfo) []*NodeLoadInfo {
  210. type rechooseNode struct {
  211. *NodeLoadInfo
  212. HasBlock bool
  213. }
  214. var rechooseNodes []*rechooseNode
  215. for _, node := range allNodes {
  216. hasBlock := false
  217. for _, id := range mostBlockNodeIDs {
  218. if id == node.Node.NodeID {
  219. hasBlock = true
  220. break
  221. }
  222. }
  223. rechooseNodes = append(rechooseNodes, &rechooseNode{
  224. NodeLoadInfo: node,
  225. HasBlock: hasBlock,
  226. })
  227. }
  228. sortedNodes := sort2.Sort(rechooseNodes, func(left *rechooseNode, right *rechooseNode) int {
  229. dm := right.LoadsRecentMonth - left.LoadsRecentMonth
  230. if dm != 0 {
  231. return dm
  232. }
  233. // 已经缓存了文件块的节点优先选择
  234. v := sort2.CmpBool(right.HasBlock, left.HasBlock)
  235. if v != 0 {
  236. return v
  237. }
  238. return right.LoadsRecentYear - left.LoadsRecentYear
  239. })
  240. return t.chooseSoManyNodes(red.RepCount, lo.Map(sortedNodes, func(node *rechooseNode, idx int) *NodeLoadInfo { return node.NodeLoadInfo }))
  241. }
  242. func (t *CheckPackageRedundancy) rechooseNodesForEC(obj stgmod.ObjectDetail, red *cdssdk.ECRedundancy, allNodes map[cdssdk.NodeID]*NodeLoadInfo) []*NodeLoadInfo {
  243. type rechooseNode struct {
  244. *NodeLoadInfo
  245. CachedBlockIndex int
  246. }
  247. var rechooseNodes []*rechooseNode
  248. for _, node := range allNodes {
  249. cachedBlockIndex := -1
  250. for _, block := range obj.Blocks {
  251. if block.NodeID == node.Node.NodeID {
  252. cachedBlockIndex = block.Index
  253. break
  254. }
  255. }
  256. rechooseNodes = append(rechooseNodes, &rechooseNode{
  257. NodeLoadInfo: node,
  258. CachedBlockIndex: cachedBlockIndex,
  259. })
  260. }
  261. sortedNodes := sort2.Sort(rechooseNodes, func(left *rechooseNode, right *rechooseNode) int {
  262. dm := right.LoadsRecentMonth - left.LoadsRecentMonth
  263. if dm != 0 {
  264. return dm
  265. }
  266. // 已经缓存了文件块的节点优先选择
  267. v := sort2.CmpBool(right.CachedBlockIndex > -1, left.CachedBlockIndex > -1)
  268. if v != 0 {
  269. return v
  270. }
  271. return right.LoadsRecentYear - left.LoadsRecentYear
  272. })
  273. // TODO 可以考虑选择已有块的节点时,能依然按照Index顺序选择
  274. return t.chooseSoManyNodes(red.N, lo.Map(sortedNodes, func(node *rechooseNode, idx int) *NodeLoadInfo { return node.NodeLoadInfo }))
  275. }
  276. func (t *CheckPackageRedundancy) chooseSoManyNodes(count int, nodes []*NodeLoadInfo) []*NodeLoadInfo {
  277. repeateCount := (count + len(nodes) - 1) / len(nodes)
  278. extedNodes := make([]*NodeLoadInfo, repeateCount*len(nodes))
  279. // 使用复制的方式将节点数扩充到要求的数量
  280. // 复制之后的结构:ABCD -> AAABBBCCCDDD
  281. for p := 0; p < repeateCount; p++ {
  282. for i, node := range nodes {
  283. putIdx := i*repeateCount + p
  284. extedNodes[putIdx] = node
  285. }
  286. }
  287. extedNodes = extedNodes[:count]
  288. var chosen []*NodeLoadInfo
  289. for len(chosen) < count {
  290. // 在每一轮内都选不同地区的节点,如果节点数不够,那么就再来一轮
  291. chosenLocations := make(map[cdssdk.LocationID]bool)
  292. for i, node := range extedNodes {
  293. if node == nil {
  294. continue
  295. }
  296. if chosenLocations[node.Node.LocationID] {
  297. continue
  298. }
  299. chosen = append(chosen, node)
  300. chosenLocations[node.Node.LocationID] = true
  301. extedNodes[i] = nil
  302. }
  303. }
  304. return chosen
  305. }
  306. func (t *CheckPackageRedundancy) noneToRep(obj stgmod.ObjectDetail, red *cdssdk.RepRedundancy, uploadNodes []*NodeLoadInfo) (*coormq.ChangeObjectRedundancyEntry, error) {
  307. if len(obj.Blocks) == 0 {
  308. return nil, fmt.Errorf("object is not cached on any nodes, cannot change its redundancy to rep")
  309. }
  310. // 如果选择的备份节点都是同一个,那么就只要上传一次
  311. uploadNodes = lo.UniqBy(uploadNodes, func(item *NodeLoadInfo) cdssdk.NodeID { return item.Node.NodeID })
  312. var blocks []stgmod.ObjectBlock
  313. for _, node := range uploadNodes {
  314. err := t.pinObject(node.Node.NodeID, obj.Object.FileHash)
  315. if err != nil {
  316. return nil, err
  317. }
  318. blocks = append(blocks, stgmod.ObjectBlock{
  319. ObjectID: obj.Object.ObjectID,
  320. Index: 0,
  321. NodeID: node.Node.NodeID,
  322. FileHash: obj.Object.FileHash,
  323. })
  324. }
  325. return &coormq.ChangeObjectRedundancyEntry{
  326. ObjectID: obj.Object.ObjectID,
  327. Redundancy: red,
  328. Blocks: blocks,
  329. }, nil
  330. }
  331. func (t *CheckPackageRedundancy) noneToEC(obj stgmod.ObjectDetail, red *cdssdk.ECRedundancy, uploadNodes []*NodeLoadInfo) (*coormq.ChangeObjectRedundancyEntry, error) {
  332. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  333. if err != nil {
  334. return nil, fmt.Errorf("new coordinator client: %w", err)
  335. }
  336. defer stgglb.CoordinatorMQPool.Release(coorCli)
  337. if len(obj.Blocks) == 0 {
  338. return nil, fmt.Errorf("object is not cached on any nodes, cannot change its redundancy to ec")
  339. }
  340. getNodes, err := coorCli.GetNodes(coormq.NewGetNodes([]cdssdk.NodeID{obj.Blocks[0].NodeID}))
  341. if err != nil {
  342. return nil, fmt.Errorf("requesting to get nodes: %w", err)
  343. }
  344. planBlder := plans.NewPlanBuilder()
  345. inputStrs := planBlder.AtAgent(getNodes.Nodes[0]).IPFSRead(obj.Object.FileHash).ChunkedSplit(red.ChunkSize, red.K, true)
  346. outputStrs := planBlder.AtAgent(getNodes.Nodes[0]).ECReconstructAny(*red, lo.Range(red.K), lo.Range(red.N), inputStrs.Streams...)
  347. for i := 0; i < red.N; i++ {
  348. outputStrs.Stream(i).GRPCSend(uploadNodes[i].Node).IPFSWrite(fmt.Sprintf("%d", i))
  349. }
  350. plan, err := planBlder.Build()
  351. if err != nil {
  352. return nil, fmt.Errorf("building io plan: %w", err)
  353. }
  354. exec, err := plans.Execute(*plan)
  355. if err != nil {
  356. return nil, fmt.Errorf("executing io plan: %w", err)
  357. }
  358. ioRet, err := exec.Wait()
  359. if err != nil {
  360. return nil, fmt.Errorf("executing io plan: %w", err)
  361. }
  362. var blocks []stgmod.ObjectBlock
  363. for i := 0; i < red.N; i++ {
  364. blocks = append(blocks, stgmod.ObjectBlock{
  365. ObjectID: obj.Object.ObjectID,
  366. Index: i,
  367. NodeID: uploadNodes[i].Node.NodeID,
  368. FileHash: ioRet.ResultValues[fmt.Sprintf("%d", i)].(string),
  369. })
  370. }
  371. return &coormq.ChangeObjectRedundancyEntry{
  372. ObjectID: obj.Object.ObjectID,
  373. Redundancy: red,
  374. Blocks: blocks,
  375. }, nil
  376. }
  377. func (t *CheckPackageRedundancy) repToRep(obj stgmod.ObjectDetail, red *cdssdk.RepRedundancy, uploadNodes []*NodeLoadInfo) (*coormq.ChangeObjectRedundancyEntry, error) {
  378. if len(obj.Blocks) == 0 {
  379. return nil, fmt.Errorf("object is not cached on any nodes, cannot change its redundancy to rep")
  380. }
  381. // 如果选择的备份节点都是同一个,那么就只要上传一次
  382. uploadNodes = lo.UniqBy(uploadNodes, func(item *NodeLoadInfo) cdssdk.NodeID { return item.Node.NodeID })
  383. for _, node := range uploadNodes {
  384. err := t.pinObject(node.Node.NodeID, obj.Object.FileHash)
  385. if err != nil {
  386. logger.WithField("ObjectID", obj.Object.ObjectID).
  387. Warn(err.Error())
  388. return nil, err
  389. }
  390. }
  391. var blocks []stgmod.ObjectBlock
  392. for _, node := range uploadNodes {
  393. // 由于更新冗余方式会删除所有Block记录然后重新填充,
  394. // 所以即使是节点跳过了上传,也需要为它添加一条Block记录
  395. blocks = append(blocks, stgmod.ObjectBlock{
  396. ObjectID: obj.Object.ObjectID,
  397. Index: 0,
  398. NodeID: node.Node.NodeID,
  399. FileHash: obj.Object.FileHash,
  400. })
  401. }
  402. return &coormq.ChangeObjectRedundancyEntry{
  403. ObjectID: obj.Object.ObjectID,
  404. Redundancy: red,
  405. Blocks: blocks,
  406. }, nil
  407. }
  408. func (t *CheckPackageRedundancy) repToEC(obj stgmod.ObjectDetail, red *cdssdk.ECRedundancy, uploadNodes []*NodeLoadInfo) (*coormq.ChangeObjectRedundancyEntry, error) {
  409. return t.noneToEC(obj, red, uploadNodes)
  410. }
  411. func (t *CheckPackageRedundancy) ecToRep(obj stgmod.ObjectDetail, srcRed *cdssdk.ECRedundancy, tarRed *cdssdk.RepRedundancy, uploadNodes []*NodeLoadInfo) (*coormq.ChangeObjectRedundancyEntry, error) {
  412. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  413. if err != nil {
  414. return nil, fmt.Errorf("new coordinator client: %w", err)
  415. }
  416. defer stgglb.CoordinatorMQPool.Release(coorCli)
  417. var chosenBlocks []stgmod.GrouppedObjectBlock
  418. var chosenBlockIndexes []int
  419. for _, block := range obj.GroupBlocks() {
  420. if len(block.NodeIDs) > 0 {
  421. chosenBlocks = append(chosenBlocks, block)
  422. chosenBlockIndexes = append(chosenBlockIndexes, block.Index)
  423. }
  424. if len(chosenBlocks) == srcRed.K {
  425. break
  426. }
  427. }
  428. if len(chosenBlocks) < srcRed.K {
  429. return nil, fmt.Errorf("no enough blocks to reconstruct the original file data")
  430. }
  431. // 如果选择的备份节点都是同一个,那么就只要上传一次
  432. uploadNodes = lo.UniqBy(uploadNodes, func(item *NodeLoadInfo) cdssdk.NodeID { return item.Node.NodeID })
  433. // 每个被选节点都在自己节点上重建原始数据
  434. planBlder := plans.NewPlanBuilder()
  435. for i := range uploadNodes {
  436. tarNode := planBlder.AtAgent(uploadNodes[i].Node)
  437. var inputs []*plans.AgentStream
  438. for _, block := range chosenBlocks {
  439. inputs = append(inputs, tarNode.IPFSRead(block.FileHash))
  440. }
  441. outputs := tarNode.ECReconstruct(*srcRed, chosenBlockIndexes, inputs...)
  442. tarNode.ChunkedJoin(srcRed.ChunkSize, outputs.Streams...).Length(obj.Object.Size).IPFSWrite(fmt.Sprintf("%d", i))
  443. }
  444. plan, err := planBlder.Build()
  445. if err != nil {
  446. return nil, fmt.Errorf("building io plan: %w", err)
  447. }
  448. exec, err := plans.Execute(*plan)
  449. if err != nil {
  450. return nil, fmt.Errorf("executing io plan: %w", err)
  451. }
  452. ioRet, err := exec.Wait()
  453. if err != nil {
  454. return nil, fmt.Errorf("executing io plan: %w", err)
  455. }
  456. var blocks []stgmod.ObjectBlock
  457. for i := range uploadNodes {
  458. blocks = append(blocks, stgmod.ObjectBlock{
  459. ObjectID: obj.Object.ObjectID,
  460. Index: 0,
  461. NodeID: uploadNodes[i].Node.NodeID,
  462. FileHash: ioRet.ResultValues[fmt.Sprintf("%d", i)].(string),
  463. })
  464. }
  465. return &coormq.ChangeObjectRedundancyEntry{
  466. ObjectID: obj.Object.ObjectID,
  467. Redundancy: tarRed,
  468. Blocks: blocks,
  469. }, nil
  470. }
  471. func (t *CheckPackageRedundancy) ecToEC(obj stgmod.ObjectDetail, srcRed *cdssdk.ECRedundancy, tarRed *cdssdk.ECRedundancy, uploadNodes []*NodeLoadInfo) (*coormq.ChangeObjectRedundancyEntry, error) {
  472. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  473. if err != nil {
  474. return nil, fmt.Errorf("new coordinator client: %w", err)
  475. }
  476. defer stgglb.CoordinatorMQPool.Release(coorCli)
  477. grpBlocks := obj.GroupBlocks()
  478. var chosenBlocks []stgmod.GrouppedObjectBlock
  479. var chosenBlockIndexes []int
  480. for _, block := range grpBlocks {
  481. if len(block.NodeIDs) > 0 {
  482. chosenBlocks = append(chosenBlocks, block)
  483. chosenBlockIndexes = append(chosenBlockIndexes, block.Index)
  484. }
  485. if len(chosenBlocks) == srcRed.K {
  486. break
  487. }
  488. }
  489. if len(chosenBlocks) < srcRed.K {
  490. return nil, fmt.Errorf("no enough blocks to reconstruct the original file data")
  491. }
  492. // 目前EC的参数都相同,所以可以不用重建出完整数据然后再分块,可以直接构建出目的节点需要的块
  493. planBlder := plans.NewPlanBuilder()
  494. var newBlocks []stgmod.ObjectBlock
  495. shouldUpdateBlocks := false
  496. for i, node := range uploadNodes {
  497. newBlock := stgmod.ObjectBlock{
  498. ObjectID: obj.Object.ObjectID,
  499. Index: i,
  500. NodeID: node.Node.NodeID,
  501. }
  502. grp, ok := lo.Find(grpBlocks, func(grp stgmod.GrouppedObjectBlock) bool { return grp.Index == i })
  503. // 如果新选中的节点已经记录在Block表中,那么就不需要任何变更
  504. if ok && lo.Contains(grp.NodeIDs, node.Node.NodeID) {
  505. newBlock.FileHash = grp.FileHash
  506. newBlocks = append(newBlocks, newBlock)
  507. continue
  508. }
  509. shouldUpdateBlocks = true
  510. // 否则就要重建出这个节点需要的块
  511. tarNode := planBlder.AtAgent(node.Node)
  512. var inputs []*plans.AgentStream
  513. for _, block := range chosenBlocks {
  514. inputs = append(inputs, tarNode.IPFSRead(block.FileHash))
  515. }
  516. // 输出只需要自己要保存的那一块
  517. tarNode.ECReconstructAny(*srcRed, chosenBlockIndexes, []int{i}, inputs...).Stream(0).IPFSWrite(fmt.Sprintf("%d", i))
  518. newBlocks = append(newBlocks, newBlock)
  519. }
  520. plan, err := planBlder.Build()
  521. if err != nil {
  522. return nil, fmt.Errorf("building io plan: %w", err)
  523. }
  524. exec, err := plans.Execute(*plan)
  525. if err != nil {
  526. return nil, fmt.Errorf("executing io plan: %w", err)
  527. }
  528. // 如果没有任何Plan,Wait会直接返回成功
  529. ret, err := exec.Wait()
  530. if err != nil {
  531. return nil, fmt.Errorf("executing io plan: %w", err)
  532. }
  533. if !shouldUpdateBlocks {
  534. return nil, nil
  535. }
  536. for k, v := range ret.ResultValues {
  537. idx, err := strconv.ParseInt(k, 10, 64)
  538. if err != nil {
  539. return nil, fmt.Errorf("parsing result key %s as index: %w", k, err)
  540. }
  541. newBlocks[idx].FileHash = v.(string)
  542. }
  543. return &coormq.ChangeObjectRedundancyEntry{
  544. ObjectID: obj.Object.ObjectID,
  545. Redundancy: tarRed,
  546. Blocks: newBlocks,
  547. }, nil
  548. }
  549. func (t *CheckPackageRedundancy) pinObject(nodeID cdssdk.NodeID, fileHash string) error {
  550. agtCli, err := stgglb.AgentMQPool.Acquire(nodeID)
  551. if err != nil {
  552. return fmt.Errorf("new agent client: %w", err)
  553. }
  554. defer stgglb.AgentMQPool.Release(agtCli)
  555. _, err = agtCli.PinObject(agtmq.ReqPinObject([]string{fileHash}, false))
  556. if err != nil {
  557. return fmt.Errorf("start pinning object: %w", err)
  558. }
  559. return nil
  560. }
  561. func init() {
  562. RegisterMessageConvertor(NewCheckPackageRedundancy)
  563. }

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