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_rep_object.go 9.1 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. package task
  2. import (
  3. "fmt"
  4. "io"
  5. "math/rand"
  6. "time"
  7. "github.com/samber/lo"
  8. "gitlink.org.cn/cloudream/client/internal/config"
  9. "gitlink.org.cn/cloudream/common/pkg/distlock/reqbuilder"
  10. "gitlink.org.cn/cloudream/common/pkg/logger"
  11. mygrpc "gitlink.org.cn/cloudream/common/utils/grpc"
  12. "gitlink.org.cn/cloudream/common/utils/ipfs"
  13. agentcaller "gitlink.org.cn/cloudream/proto"
  14. agtcli "gitlink.org.cn/cloudream/rabbitmq/client/agent"
  15. ramsg "gitlink.org.cn/cloudream/rabbitmq/message"
  16. agtmsg "gitlink.org.cn/cloudream/rabbitmq/message/agent"
  17. coormsg "gitlink.org.cn/cloudream/rabbitmq/message/coordinator"
  18. "google.golang.org/grpc"
  19. "google.golang.org/grpc/credentials/insecure"
  20. )
  21. // UploadObjects和UploadRepResults一一对应
  22. type UploadRepObject struct {
  23. userID int
  24. bucketID int
  25. repCount int
  26. UploadObjects []UploadObject
  27. UploadRepResults []UploadRepResult
  28. }
  29. type UploadObject struct {
  30. ObjectName string
  31. File io.ReadCloser
  32. FileSize int64
  33. }
  34. type UploadRepResult struct {
  35. Error error
  36. ResultFileHash string
  37. ObjectID int64
  38. }
  39. func NewUploadRepObject(userID int, bucketID int, UploadObjects []UploadObject, repCount int) *UploadRepObject {
  40. return &UploadRepObject{
  41. userID: userID,
  42. bucketID: bucketID,
  43. UploadObjects: UploadObjects,
  44. repCount: repCount,
  45. }
  46. }
  47. func (t *UploadRepObject) Execute(ctx TaskContext, complete CompleteFn) {
  48. UploadRepResults, err := t.do(ctx)
  49. t.UploadRepResults = UploadRepResults
  50. complete(err, CompleteOption{
  51. RemovingDelay: time.Minute,
  52. })
  53. }
  54. func (t *UploadRepObject) do(ctx TaskContext) ([]UploadRepResult, error) {
  55. reqBlder := reqbuilder.NewBuilder()
  56. for _, uploadObject := range t.UploadObjects {
  57. reqBlder.Metadata().
  58. // 用于防止创建了多个同名对象
  59. Object().CreateOne(t.bucketID, uploadObject.ObjectName)
  60. }
  61. mutex, err := reqBlder.
  62. Metadata().
  63. // 用于判断用户是否有桶的权限
  64. UserBucket().ReadOne(t.userID, t.bucketID).
  65. // 用于查询可用的上传节点
  66. Node().ReadAny().
  67. // 用于设置Rep配置
  68. ObjectRep().CreateAny().
  69. // 用于创建Cache记录
  70. Cache().CreateAny().
  71. MutexLock(ctx.DistLock)
  72. if err != nil {
  73. return nil, fmt.Errorf("acquire locks failed, err: %w", err)
  74. }
  75. defer mutex.Unlock()
  76. uploadObjs := []struct {
  77. UploadObject UploadObject
  78. nodes []ramsg.RespNode
  79. }{}
  80. for _, uploadObject := range t.UploadObjects {
  81. nodes, err := t.preUploadSingleObject(ctx, uploadObject)
  82. if err != nil {
  83. // 不满足上传条件,直接记录结果
  84. result := UploadRepResult{
  85. Error: err,
  86. ResultFileHash: "",
  87. ObjectID: 0,
  88. }
  89. t.UploadRepResults = append(t.UploadRepResults, result)
  90. continue
  91. }
  92. obj := struct {
  93. UploadObject UploadObject
  94. nodes []ramsg.RespNode
  95. }{
  96. UploadObject: uploadObject,
  97. nodes: nodes,
  98. }
  99. uploadObjs = append(uploadObjs, obj)
  100. }
  101. // 不满足上传条件,返回结果
  102. if len(uploadObjs) != len(t.UploadObjects) {
  103. return t.UploadRepResults, fmt.Errorf("Folder does not meet the upload requirements.")
  104. }
  105. //上传文件夹
  106. for _, uploadObj := range uploadObjs {
  107. objectID, fileHash, err := t.uploadSingleObject(ctx, uploadObj.UploadObject, uploadObj.nodes)
  108. if err != nil {
  109. // 上传文件时出现错误,记录结果
  110. result := UploadRepResult{
  111. Error: err,
  112. ResultFileHash: "",
  113. ObjectID: 0,
  114. }
  115. t.UploadRepResults = append(t.UploadRepResults, result)
  116. continue
  117. }
  118. // 文件上传成功,记录结果
  119. result := UploadRepResult{
  120. Error: err,
  121. ResultFileHash: fileHash,
  122. ObjectID: objectID,
  123. }
  124. t.UploadRepResults = append(t.UploadRepResults, result)
  125. }
  126. return t.UploadRepResults, nil
  127. }
  128. // 检查单个文件是否能够上传
  129. func (t *UploadRepObject) preUploadSingleObject(ctx TaskContext, uploadObject UploadObject) ([]ramsg.RespNode, error) {
  130. //发送写请求,请求Coor分配写入节点Ip
  131. // fmt.Printf("uploadObject: %v\n", uploadObject)
  132. repWriteResp, err := ctx.Coordinator.PreUploadRepObject(coormsg.NewPreUploadRepObjectBody(t.bucketID, uploadObject.ObjectName, uploadObject.FileSize, t.userID, config.Cfg().ExternalIP))
  133. if err != nil {
  134. return nil, fmt.Errorf("pre upload rep object: %w", err)
  135. }
  136. if len(repWriteResp.Nodes) == 0 {
  137. return nil, fmt.Errorf("no node to upload file")
  138. }
  139. return repWriteResp.Nodes, nil
  140. }
  141. // 上传文件
  142. func (t *UploadRepObject) uploadSingleObject(ctx TaskContext, uploadObject UploadObject, nodes []ramsg.RespNode) (int64, string, error) {
  143. uploadNode := t.chooseUploadNode(nodes)
  144. var fileHash string
  145. uploadedNodeIDs := []int{}
  146. willUploadToNode := true
  147. // 本地有IPFS,则直接从本地IPFS上传
  148. if ctx.IPFS != nil {
  149. logger.Infof("try to use local IPFS to upload file")
  150. var err error
  151. fileHash, err = uploadToLocalIPFS(ctx.IPFS, uploadObject.File, uploadNode.ID)
  152. if err != nil {
  153. logger.Warnf("upload to local IPFS failed, so try to upload to node %d, err: %s", uploadNode.ID, err.Error())
  154. } else {
  155. willUploadToNode = false
  156. }
  157. }
  158. // 否则发送到agent上传
  159. if willUploadToNode {
  160. // 如果客户端与节点在同一个地域,则使用内网地址连接节点
  161. nodeIP := uploadNode.ExternalIP
  162. if uploadNode.IsSameLocation {
  163. nodeIP = uploadNode.LocalIP
  164. logger.Infof("client and node %d are at the same location, use local ip\n", uploadNode.ID)
  165. }
  166. mutex, err := reqbuilder.NewBuilder().
  167. // 防止上传的副本被清除
  168. IPFS().CreateAnyRep(uploadNode.ID).
  169. MutexLock(ctx.DistLock)
  170. if err != nil {
  171. return 0, "", fmt.Errorf("acquire locks failed, err: %w", err)
  172. }
  173. defer mutex.Unlock()
  174. fileHash, err = uploadToNode(uploadObject.File, nodeIP)
  175. if err != nil {
  176. return 0, "", fmt.Errorf("upload to node %s failed, err: %w", nodeIP, err)
  177. }
  178. uploadedNodeIDs = append(uploadedNodeIDs, uploadNode.ID)
  179. }
  180. // 记录写入的文件的Hash
  181. createResp, err := ctx.Coordinator.CreateRepObject(coormsg.NewCreateRepObject(t.bucketID, uploadObject.ObjectName, uploadObject.FileSize, t.repCount, t.userID, uploadedNodeIDs, fileHash))
  182. if err != nil {
  183. return 0, "", fmt.Errorf("creating rep object: %w", err)
  184. }
  185. return createResp.ObjectID, fileHash, nil
  186. }
  187. // chooseUploadNode 选择一个上传文件的节点
  188. // 1. 从与当前客户端相同地域的节点中随机选一个
  189. // 2. 没有用的话从所有节点中随机选一个
  190. func (t *UploadRepObject) chooseUploadNode(nodes []ramsg.RespNode) ramsg.RespNode {
  191. sameLocationNodes := lo.Filter(nodes, func(e ramsg.RespNode, i int) bool { return e.IsSameLocation })
  192. if len(sameLocationNodes) > 0 {
  193. return sameLocationNodes[rand.Intn(len(sameLocationNodes))]
  194. }
  195. return nodes[rand.Intn(len(nodes))]
  196. }
  197. func uploadToNode(file io.ReadCloser, nodeIP string) (string, error) {
  198. // 建立grpc连接,发送请求
  199. grpcAddr := fmt.Sprintf("%s:%d", nodeIP, config.Cfg().GRPCPort)
  200. grpcCon, err := grpc.Dial(grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
  201. if err != nil {
  202. return "", fmt.Errorf("connect to grpc server at %s failed, err: %w", grpcAddr, err)
  203. }
  204. defer grpcCon.Close()
  205. client := agentcaller.NewFileTransportClient(grpcCon)
  206. upload, err := mygrpc.SendFileAsStream(client)
  207. if err != nil {
  208. return "", fmt.Errorf("request to send file failed, err: %w", err)
  209. }
  210. // 发送文件数据
  211. _, err = io.Copy(upload, file)
  212. if err != nil {
  213. // 发生错误则关闭连接
  214. upload.Abort(io.ErrClosedPipe)
  215. return "", fmt.Errorf("copy file date to upload stream failed, err: %w", err)
  216. }
  217. // 发送EOF消息,并获得FileHash
  218. fileHash, err := upload.Finish()
  219. if err != nil {
  220. upload.Abort(io.ErrClosedPipe)
  221. return "", fmt.Errorf("send EOF failed, err: %w", err)
  222. }
  223. return fileHash, nil
  224. }
  225. func uploadToLocalIPFS(ipfs *ipfs.IPFS, file io.ReadCloser, nodeID int) (string, error) {
  226. // 从本地IPFS上传文件
  227. writer, err := ipfs.CreateFile()
  228. if err != nil {
  229. return "", fmt.Errorf("create IPFS file failed, err: %w", err)
  230. }
  231. _, err = io.Copy(writer, file)
  232. if err != nil {
  233. return "", fmt.Errorf("copy file data to IPFS failed, err: %w", err)
  234. }
  235. fileHash, err := writer.Finish()
  236. if err != nil {
  237. return "", fmt.Errorf("finish writing IPFS failed, err: %w", err)
  238. }
  239. // 然后让最近节点pin本地上传的文件
  240. agentClient, err := agtcli.NewClient(nodeID, &config.Cfg().RabbitMQ)
  241. if err != nil {
  242. return "", fmt.Errorf("create agent client to %d failed, err: %w", nodeID, err)
  243. }
  244. defer agentClient.Close()
  245. pinObjResp, err := agentClient.StartPinningObject(agtmsg.NewStartPinningObject(fileHash))
  246. if err != nil {
  247. return "", fmt.Errorf("start pinning object: %w", err)
  248. }
  249. for {
  250. waitResp, err := agentClient.WaitPinningObject(agtmsg.NewWaitPinningObject(pinObjResp.TaskID, int64(time.Second)*5))
  251. if err != nil {
  252. return "", fmt.Errorf("waitting pinning object: %w", err)
  253. }
  254. if waitResp.IsComplete {
  255. if waitResp.Error != "" {
  256. return "", fmt.Errorf("agent pinning object: %s", waitResp.Error)
  257. }
  258. break
  259. }
  260. }
  261. return fileHash, nil
  262. }

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