package obs import ( "context" "errors" "fmt" "time" "github.com/aws/aws-sdk-go-v2/aws" awss3 "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" oms "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/oms/v2" "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/oms/v2/model" omsregion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/oms/v2/region" "gitlink.org.cn/cloudream/common/utils/os2" stgs3 "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/s3" stgtypes "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types" jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types" ) type S2STransfer struct { detail *jcstypes.UserSpaceDetail stgType *jcstypes.OBSType cred *jcstypes.OBSCred feat *jcstypes.S2STransferFeature taskID *int64 omsCli *oms.OmsClient } func NewS2STransfer(detail *jcstypes.UserSpaceDetail, stgType *jcstypes.OBSType, cred *jcstypes.OBSCred, feat *jcstypes.S2STransferFeature) *S2STransfer { return &S2STransfer{ detail: detail, stgType: stgType, cred: cred, feat: feat, } } // 判断是否能从指定的源存储中直传到当前存储的目的路径 func (*S2STransfer) CanTransfer(src, dst *jcstypes.UserSpaceDetail) bool { req := makeRequest(src, jcstypes.JPath{}) return req != nil } // 执行数据直传。返回传输后的文件路径 func (s *S2STransfer) Transfer(ctx context.Context, src *jcstypes.UserSpaceDetail, srcPath jcstypes.JPath, dstPath jcstypes.JPath) (stgtypes.FileInfo, error) { req := makeRequest(src, srcPath) if req == nil { return stgtypes.FileInfo{}, fmt.Errorf("unsupported source storage type: %T", src.UserSpace.Storage) } auth, err := basic.NewCredentialsBuilder(). WithAk(s.cred.AK). WithSk(s.cred.SK). WithProjectId(s.stgType.ProjectID). SafeBuild() if err != nil { return stgtypes.FileInfo{}, err } region, err := omsregion.SafeValueOf(s.stgType.Region) if err != nil { return stgtypes.FileInfo{}, err } cli, err := oms.OmsClientBuilder(). WithRegion(region). WithCredential(auth). SafeBuild() if err != nil { return stgtypes.FileInfo{}, err } // 先上传成一个临时文件 tempDir := stgs3.JoinKey(s.detail.UserSpace.WorkingDir.String(), stgtypes.TempWorkingDir) tempPrefix := stgs3.JoinKey(tempDir, os2.GenerateRandomFileName(10)) + "/" taskType := model.GetCreateTaskReqTaskTypeEnum().OBJECT s.omsCli = oms.NewOmsClient(cli) resp, err := s.omsCli.CreateTask(&model.CreateTaskRequest{ Body: &model.CreateTaskReq{ TaskType: &taskType, SrcNode: req, DstNode: &model.DstNodeReq{ Region: s.stgType.Region, Ak: s.cred.AK, Sk: s.cred.SK, Bucket: s.stgType.Bucket, SavePrefix: &tempPrefix, }, }, }) if err != nil { return stgtypes.FileInfo{}, fmt.Errorf("create task: %w", err) } s.taskID = resp.Id // 轮询任务状态,直到完成 size, err := s.waitTask(ctx, *resp.Id) if err != nil { return stgtypes.FileInfo{}, fmt.Errorf("wait task: %w", err) } // 传输完成后,将文件名改成目标路径 obsCli, bkt, err := createClient(s.stgType, s.cred) if err != nil { return stgtypes.FileInfo{}, err } _, err = obsCli.CopyObject(ctx, &awss3.CopyObjectInput{ Bucket: aws.String(bkt), CopySource: aws.String(stgs3.JoinKey(bkt, tempPrefix, srcPath.String())), Key: aws.String(dstPath.String()), }) if err != nil { return stgtypes.FileInfo{}, fmt.Errorf("copy object: %w", err) } return stgtypes.FileInfo{ Path: dstPath, Size: size, Hash: "", }, nil } func (s *S2STransfer) waitTask(ctx context.Context, taskId int64) (int64, error) { ticker := time.NewTicker(time.Second * 5) defer ticker.Stop() failures := 0 for { resp, err := s.omsCli.ShowTask(&model.ShowTaskRequest{ TaskId: fmt.Sprintf("%v", taskId), }) if err != nil { if failures < 3 { failures++ continue } return 0, fmt.Errorf("show task failed too many times: %w", err) } failures = 0 if *resp.Status == 3 { return 0, fmt.Errorf("task stopped") } if *resp.Status == 4 { return 0, errors.New(resp.ErrorReason.String()) } if *resp.Status == 5 { return *resp.CompleteSize, nil } select { case <-ticker.C: continue case <-ctx.Done(): return 0, ctx.Err() } } } func (s *S2STransfer) Close() { if s.taskID != nil { s.omsCli.StopTask(&model.StopTaskRequest{ TaskId: fmt.Sprintf("%v", *s.taskID), }) s.omsCli.DeleteTask(&model.DeleteTaskRequest{ TaskId: fmt.Sprintf("%v", *s.taskID), }) } } func makeRequest(srcStg *jcstypes.UserSpaceDetail, srcPath jcstypes.JPath) *model.SrcNodeReq { switch srcType := srcStg.UserSpace.Storage.(type) { case *jcstypes.OBSType: cloudType := "HuaweiCloud" cred, ok := srcStg.UserSpace.Credential.(*jcstypes.OBSCred) if !ok { return nil } return &model.SrcNodeReq{ CloudType: &cloudType, Region: &srcType.Region, Ak: &cred.AK, Sk: &cred.SK, Bucket: &srcType.Bucket, ObjectKey: &[]string{srcPath.String()}, } default: return nil } }