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.

s2s.go 5.1 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. package obs
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "time"
  7. "github.com/aws/aws-sdk-go-v2/aws"
  8. awss3 "github.com/aws/aws-sdk-go-v2/service/s3"
  9. "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
  10. oms "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/oms/v2"
  11. "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/oms/v2/model"
  12. omsregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/oms/v2/region"
  13. "gitlink.org.cn/cloudream/common/utils/os2"
  14. stgs3 "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/s3"
  15. stgtypes "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
  16. jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types"
  17. )
  18. type S2STransfer struct {
  19. detail *jcstypes.UserSpaceDetail
  20. stgType *jcstypes.OBSType
  21. cred *jcstypes.OBSCred
  22. feat *jcstypes.S2STransferFeature
  23. taskID *int64
  24. omsCli *oms.OmsClient
  25. }
  26. func NewS2STransfer(detail *jcstypes.UserSpaceDetail, stgType *jcstypes.OBSType, cred *jcstypes.OBSCred, feat *jcstypes.S2STransferFeature) *S2STransfer {
  27. return &S2STransfer{
  28. detail: detail,
  29. stgType: stgType,
  30. cred: cred,
  31. feat: feat,
  32. }
  33. }
  34. // 判断是否能从指定的源存储中直传到当前存储的目的路径
  35. func (*S2STransfer) CanTransfer(src, dst *jcstypes.UserSpaceDetail) bool {
  36. req := makeRequest(src, jcstypes.JPath{})
  37. return req != nil
  38. }
  39. // 执行数据直传。返回传输后的文件路径
  40. func (s *S2STransfer) Transfer(ctx context.Context, src *jcstypes.UserSpaceDetail, srcPath jcstypes.JPath, dstPath jcstypes.JPath) (stgtypes.FileInfo, error) {
  41. req := makeRequest(src, srcPath)
  42. if req == nil {
  43. return stgtypes.FileInfo{}, fmt.Errorf("unsupported source storage type: %T", src.UserSpace.Storage)
  44. }
  45. auth, err := basic.NewCredentialsBuilder().
  46. WithAk(s.cred.AK).
  47. WithSk(s.cred.SK).
  48. WithProjectId(s.stgType.ProjectID).
  49. SafeBuild()
  50. if err != nil {
  51. return stgtypes.FileInfo{}, err
  52. }
  53. region, err := omsregion.SafeValueOf(s.stgType.Region)
  54. if err != nil {
  55. return stgtypes.FileInfo{}, err
  56. }
  57. cli, err := oms.OmsClientBuilder().
  58. WithRegion(region).
  59. WithCredential(auth).
  60. SafeBuild()
  61. if err != nil {
  62. return stgtypes.FileInfo{}, err
  63. }
  64. // 先上传成一个临时文件
  65. tempDir := stgs3.JoinKey(s.detail.UserSpace.WorkingDir.String(), stgtypes.TempWorkingDir)
  66. tempPrefix := stgs3.JoinKey(tempDir, os2.GenerateRandomFileName(10)) + "/"
  67. taskType := model.GetCreateTaskReqTaskTypeEnum().OBJECT
  68. s.omsCli = oms.NewOmsClient(cli)
  69. resp, err := s.omsCli.CreateTask(&model.CreateTaskRequest{
  70. Body: &model.CreateTaskReq{
  71. TaskType: &taskType,
  72. SrcNode: req,
  73. DstNode: &model.DstNodeReq{
  74. Region: s.stgType.Region,
  75. Ak: s.cred.AK,
  76. Sk: s.cred.SK,
  77. Bucket: s.stgType.Bucket,
  78. SavePrefix: &tempPrefix,
  79. },
  80. },
  81. })
  82. if err != nil {
  83. return stgtypes.FileInfo{}, fmt.Errorf("create task: %w", err)
  84. }
  85. s.taskID = resp.Id
  86. // 轮询任务状态,直到完成
  87. size, err := s.waitTask(ctx, *resp.Id)
  88. if err != nil {
  89. return stgtypes.FileInfo{}, fmt.Errorf("wait task: %w", err)
  90. }
  91. // 传输完成后,将文件名改成目标路径
  92. obsCli, bkt, err := createClient(s.stgType, s.cred)
  93. if err != nil {
  94. return stgtypes.FileInfo{}, err
  95. }
  96. _, err = obsCli.CopyObject(ctx, &awss3.CopyObjectInput{
  97. Bucket: aws.String(bkt),
  98. CopySource: aws.String(stgs3.JoinKey(bkt, tempPrefix, srcPath.String())),
  99. Key: aws.String(dstPath.String()),
  100. })
  101. if err != nil {
  102. return stgtypes.FileInfo{}, fmt.Errorf("copy object: %w", err)
  103. }
  104. return stgtypes.FileInfo{
  105. Path: dstPath,
  106. Size: size,
  107. Hash: "",
  108. }, nil
  109. }
  110. func (s *S2STransfer) waitTask(ctx context.Context, taskId int64) (int64, error) {
  111. ticker := time.NewTicker(time.Second * 5)
  112. defer ticker.Stop()
  113. failures := 0
  114. for {
  115. resp, err := s.omsCli.ShowTask(&model.ShowTaskRequest{
  116. TaskId: fmt.Sprintf("%v", taskId),
  117. })
  118. if err != nil {
  119. if failures < 3 {
  120. failures++
  121. continue
  122. }
  123. return 0, fmt.Errorf("show task failed too many times: %w", err)
  124. }
  125. failures = 0
  126. if *resp.Status == 3 {
  127. return 0, fmt.Errorf("task stopped")
  128. }
  129. if *resp.Status == 4 {
  130. return 0, errors.New(resp.ErrorReason.String())
  131. }
  132. if *resp.Status == 5 {
  133. return *resp.CompleteSize, nil
  134. }
  135. select {
  136. case <-ticker.C:
  137. continue
  138. case <-ctx.Done():
  139. return 0, ctx.Err()
  140. }
  141. }
  142. }
  143. func (s *S2STransfer) Close() {
  144. if s.taskID != nil {
  145. s.omsCli.StopTask(&model.StopTaskRequest{
  146. TaskId: fmt.Sprintf("%v", *s.taskID),
  147. })
  148. s.omsCli.DeleteTask(&model.DeleteTaskRequest{
  149. TaskId: fmt.Sprintf("%v", *s.taskID),
  150. })
  151. }
  152. }
  153. func makeRequest(srcStg *jcstypes.UserSpaceDetail, srcPath jcstypes.JPath) *model.SrcNodeReq {
  154. switch srcType := srcStg.UserSpace.Storage.(type) {
  155. case *jcstypes.OBSType:
  156. cloudType := "HuaweiCloud"
  157. cred, ok := srcStg.UserSpace.Credential.(*jcstypes.OBSCred)
  158. if !ok {
  159. return nil
  160. }
  161. return &model.SrcNodeReq{
  162. CloudType: &cloudType,
  163. Region: &srcType.Region,
  164. Ak: &cred.AK,
  165. Sk: &cred.SK,
  166. Bucket: &srcType.Bucket,
  167. ObjectKey: &[]string{srcPath.String()},
  168. }
  169. default:
  170. return nil
  171. }
  172. }

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