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.

object.go 8.2 kB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package services
  2. import (
  3. "fmt"
  4. "io"
  5. "math/rand"
  6. "gitlink.org.cn/cloudream/client/internal/config"
  7. "gitlink.org.cn/cloudream/db/model"
  8. agentcaller "gitlink.org.cn/cloudream/proto"
  9. coormsg "gitlink.org.cn/cloudream/rabbitmq/message/coordinator"
  10. "gitlink.org.cn/cloudream/utils/consts"
  11. "gitlink.org.cn/cloudream/utils/consts/errorcode"
  12. mygrpc "gitlink.org.cn/cloudream/utils/grpc"
  13. myio "gitlink.org.cn/cloudream/utils/io"
  14. "google.golang.org/grpc"
  15. "google.golang.org/grpc/credentials/insecure"
  16. lo "github.com/samber/lo"
  17. )
  18. type ObjectService struct {
  19. *Service
  20. }
  21. func ObjectSvc(svc *Service) *ObjectService {
  22. return &ObjectService{Service: svc}
  23. }
  24. func (svc *ObjectService) GetObject(userID int, objectID int) (model.Object, error) {
  25. // TODO
  26. panic("not implement yet")
  27. }
  28. func (svc *ObjectService) DownloadObject(userID int, objectID int) (io.ReadCloser, error) {
  29. preDownloadResp, err := svc.coordinator.PreDownloadObject(coormsg.NewReadCommandBody(objectID, userID, config.Cfg().ExternalIP))
  30. if err != nil {
  31. return nil, fmt.Errorf("request to coordinator failed, err: %w", err)
  32. }
  33. if preDownloadResp.ErrorCode != errorcode.OK {
  34. return nil, fmt.Errorf("coordinator operation failed, code: %s, message: %s", preDownloadResp.ErrorCode, preDownloadResp.ErrorMessage)
  35. }
  36. switch preDownloadResp.Body.Redundancy {
  37. case consts.REDUNDANCY_REP:
  38. if len(preDownloadResp.Body.Entries) == 0 {
  39. return nil, fmt.Errorf("no node has this file")
  40. }
  41. // 选择下载节点
  42. entry := svc.chooseDownloadNode(preDownloadResp.Body.Entries)
  43. // 如果客户端与节点在同一个地域,则使用内网地址连接节点
  44. nodeIP := entry.NodeExternalIP
  45. if entry.IsSameLocation {
  46. nodeIP = entry.NodeLocalIP
  47. // TODO 以后考虑用log
  48. fmt.Printf("client and node %d are at the same location, use local ip\n", entry.NodeID)
  49. }
  50. reader, err := svc.downloadAsRepObject(nodeIP, entry.FileHash)
  51. if err != nil {
  52. return nil, fmt.Errorf("rep read failed, err: %w", err)
  53. }
  54. return reader, nil
  55. //case consts.REDUNDANCY_EC:
  56. // TODO EC部分的代码要考虑重构
  57. // ecRead(readResp.FileSizeInBytes, readResp.NodeIPs, readResp.Hashes, readResp.BlockIDs, *readResp.ECName)
  58. }
  59. return nil, fmt.Errorf("unsupported redundancy type: %s", preDownloadResp.Body.Redundancy)
  60. }
  61. // chooseDownloadNode 选择一个下载节点
  62. // 1. 从与当前客户端相同地域的节点中随机选一个
  63. // 2. 没有用的话从所有节点中随机选一个
  64. func (svc *ObjectService) chooseDownloadNode(entries []coormsg.PreDownloadObjectRespEntry) coormsg.PreDownloadObjectRespEntry {
  65. sameLocationEntries := lo.Filter(entries, func(e coormsg.PreDownloadObjectRespEntry, i int) bool { return e.IsSameLocation })
  66. if len(sameLocationEntries) > 0 {
  67. return sameLocationEntries[rand.Intn(len(sameLocationEntries))]
  68. }
  69. return entries[rand.Intn(len(entries))]
  70. }
  71. func (svc *ObjectService) downloadAsRepObject(nodeIP string, fileHash string) (io.ReadCloser, error) {
  72. // 连接grpc
  73. grpcAddr := fmt.Sprintf("%s:%d", nodeIP, config.Cfg().GRPCPort)
  74. conn, err := grpc.Dial(grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
  75. if err != nil {
  76. return nil, fmt.Errorf("connect to grpc server at %s failed, err: %w", grpcAddr, err)
  77. }
  78. /*
  79. TO DO: 判断本地有没有ipfs daemon、能否获取相应对象的cid
  80. 如果本地有ipfs daemon且能获取相应对象的cid,则获取对象cid对应的ipfsblock的cid,通过ipfs网络获取这些ipfsblock
  81. 否则,像目前一样,使用grpc向指定节点获取
  82. */
  83. // 下载文件
  84. client := agentcaller.NewFileTransportClient(conn)
  85. reader, err := mygrpc.GetFileAsStream(client, fileHash)
  86. if err != nil {
  87. conn.Close()
  88. return nil, fmt.Errorf("request to get file failed, err: %w", err)
  89. }
  90. reader = myio.AfterReadClosed(reader, func(io.ReadCloser) { conn.Close() })
  91. return reader, nil
  92. }
  93. func (svc *ObjectService) UploadRepObject(userID int, bucketID int, objectName string, file io.ReadCloser, fileSize int64, repNum int) error {
  94. //发送写请求,请求Coor分配写入节点Ip
  95. repWriteResp, err := svc.coordinator.PreUploadRepObject(coormsg.NewPreUploadRepObjectBody(bucketID, objectName, fileSize, userID, config.Cfg().ExternalIP))
  96. if err != nil {
  97. return fmt.Errorf("request to coordinator failed, err: %w", err)
  98. }
  99. if repWriteResp.ErrorCode != errorcode.OK {
  100. return fmt.Errorf("coordinator RepWrite failed, code: %s, message: %s", repWriteResp.ErrorCode, repWriteResp.ErrorMessage)
  101. }
  102. /*
  103. TO DO ss: 判断本地有没有ipfs daemon、能否与目标agent的ipfs daemon连通、本地ipfs目录空间是否充足
  104. 如果本地有ipfs daemon、能与目标agent的ipfs daemon连通、本地ipfs目录空间充足,将所有内容写入本地ipfs目录,得到对象的cid,发送cid给目标agent让其pin相应的对象
  105. 否则,像目前一样,使用grpc向指定节点获取
  106. */
  107. uploadNode := svc.chooseUploadNode(repWriteResp.Body.Nodes)
  108. // 如果客户端与节点在同一个地域,则使用内网地址连接节点
  109. nodeIP := uploadNode.ExternalIP
  110. if uploadNode.IsSameLocation {
  111. nodeIP = uploadNode.LocalIP
  112. // TODO 以后考虑用log
  113. fmt.Printf("client and node %d are at the same location, use local ip\n", uploadNode.ID)
  114. }
  115. // 建立grpc连接,发送请求
  116. grpcAddr := fmt.Sprintf("%s:%d", nodeIP, config.Cfg().GRPCPort)
  117. grpcCon, err := grpc.Dial(grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
  118. if err != nil {
  119. return fmt.Errorf("connect to grpc server at %s failed, err: %w", grpcAddr, err)
  120. }
  121. defer grpcCon.Close()
  122. client := agentcaller.NewFileTransportClient(grpcCon)
  123. upload, err := mygrpc.SendFileAsStream(client)
  124. if err != nil {
  125. return fmt.Errorf("request to send file failed, err: %w", err)
  126. }
  127. // 发送文件数据
  128. err = svc.sendFileData(file, upload)
  129. if err != nil {
  130. // 发生错误则关闭连接
  131. upload.Abort(io.ErrClosedPipe)
  132. return err
  133. }
  134. // 发送EOF消息,并获得FileHash
  135. fileHash, err := upload.Finish()
  136. if err != nil {
  137. upload.Abort(io.ErrClosedPipe)
  138. return fmt.Errorf("send EOF failed, err: %w", err)
  139. }
  140. // 记录写入的文件的Hash
  141. createObjectResp, err := svc.coordinator.CreateRepObject(coormsg.NewCreateRepObjectBody(bucketID, objectName, fileSize, repNum, userID, uploadNode.ID, fileHash))
  142. if err != nil {
  143. return fmt.Errorf("request to coordinator failed, err: %w", err)
  144. }
  145. if createObjectResp.ErrorCode != errorcode.OK {
  146. return fmt.Errorf("coordinator WriteRepHash failed, code: %s, message: %s", createObjectResp.ErrorCode, createObjectResp.ErrorMessage)
  147. }
  148. return nil
  149. }
  150. // chooseUploadNode 选择一个上传文件的节点
  151. // 1. 从与当前客户端相同地域的节点中随机选一个
  152. // 2. 没有用的话从所有节点中随机选一个
  153. func (svc *ObjectService) chooseUploadNode(nodes []coormsg.PreUploadRespNode) coormsg.PreUploadRespNode {
  154. sameLocationNodes := lo.Filter(nodes, func(e coormsg.PreUploadRespNode, i int) bool { return e.IsSameLocation })
  155. if len(sameLocationNodes) > 0 {
  156. return sameLocationNodes[rand.Intn(len(sameLocationNodes))]
  157. }
  158. return nodes[rand.Intn(len(nodes))]
  159. }
  160. func (svc *ObjectService) sendFileData(file io.ReadCloser, upload mygrpc.FileWriteCloser[string]) error {
  161. // 发送数据缓冲区
  162. buf := make([]byte, 2048)
  163. for {
  164. // 读取文件数据
  165. readCnt, err := file.Read(buf)
  166. if readCnt > 0 {
  167. err := myio.WriteAll(upload, buf[:readCnt])
  168. if err != nil {
  169. return fmt.Errorf("send file data failed, err: %w", err)
  170. }
  171. }
  172. // 文件读取完毕
  173. if err == io.EOF {
  174. break
  175. }
  176. if err != nil {
  177. return fmt.Errorf("read file data failed, err: %w", err)
  178. }
  179. }
  180. return nil
  181. }
  182. func (svc *ObjectService) UploadECObject(userID int, file io.ReadCloser, fileSize int64, ecName string) error {
  183. // TODO
  184. panic("not implement yet")
  185. }
  186. func (svc *ObjectService) DeleteObject(userID int, objectID int) error {
  187. resp, err := svc.coordinator.DeleteObject(coormsg.NewDeleteObjectBody(userID, objectID))
  188. if err != nil {
  189. return fmt.Errorf("request to coordinator failed, err: %w", err)
  190. }
  191. if !resp.IsOK() {
  192. return fmt.Errorf("create bucket objects failed, code: %s, message: %s", resp.ErrorCode, resp.ErrorMessage)
  193. }
  194. return nil
  195. }

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