github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/backend/oracleobjectstorage/copy.go (about) 1 //go:build !plan9 && !solaris && !js 2 3 package oracleobjectstorage 4 5 import ( 6 "context" 7 "fmt" 8 "strings" 9 "time" 10 11 "github.com/oracle/oci-go-sdk/v65/common" 12 "github.com/oracle/oci-go-sdk/v65/objectstorage" 13 "github.com/rclone/rclone/fs" 14 ) 15 16 // ------------------------------------------------------------ 17 // Implement Copier is an optional interfaces for Fs 18 //------------------------------------------------------------ 19 20 // Copy src to this remote using server-side copy operations. 21 // This is stored with the remote path given 22 // It returns the destination Object and a possible error 23 // Will only be called if src.Fs().Name() == f.Name() 24 // If it isn't possible then return fs.ErrorCantCopy 25 func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 26 // fs.Debugf(f, "copying %v to %v", src.Remote(), remote) 27 srcObj, ok := src.(*Object) 28 if !ok { 29 // fs.Debugf(src, "Can't copy - not same remote type") 30 return nil, fs.ErrorCantCopy 31 } 32 // Temporary Object under construction 33 dstObj := &Object{ 34 fs: f, 35 remote: remote, 36 } 37 err := f.copy(ctx, dstObj, srcObj) 38 if err != nil { 39 return nil, err 40 } 41 return f.NewObject(ctx, remote) 42 } 43 44 // copy does a server-side copy from dstObj <- srcObj 45 // 46 // If newInfo is nil then the metadata will be copied otherwise it 47 // will be replaced with newInfo 48 func (f *Fs) copy(ctx context.Context, dstObj *Object, srcObj *Object) (err error) { 49 srcBucket, srcPath := srcObj.split() 50 dstBucket, dstPath := dstObj.split() 51 if dstBucket != srcBucket { 52 exists, err := f.bucketExists(ctx, dstBucket) 53 if err != nil { 54 return err 55 } 56 if !exists { 57 err = f.makeBucket(ctx, dstBucket) 58 if err != nil { 59 return err 60 } 61 } 62 } 63 copyObjectDetails := objectstorage.CopyObjectDetails{ 64 SourceObjectName: common.String(srcPath), 65 DestinationRegion: common.String(dstObj.fs.opt.Region), 66 DestinationNamespace: common.String(dstObj.fs.opt.Namespace), 67 DestinationBucket: common.String(dstBucket), 68 DestinationObjectName: common.String(dstPath), 69 DestinationObjectMetadata: metadataWithOpcPrefix(srcObj.meta), 70 } 71 req := objectstorage.CopyObjectRequest{ 72 NamespaceName: common.String(srcObj.fs.opt.Namespace), 73 BucketName: common.String(srcBucket), 74 CopyObjectDetails: copyObjectDetails, 75 } 76 useBYOKCopyObject(f, &req) 77 var resp objectstorage.CopyObjectResponse 78 err = f.pacer.Call(func() (bool, error) { 79 resp, err = f.srv.CopyObject(ctx, req) 80 return shouldRetry(ctx, resp.HTTPResponse(), err) 81 }) 82 if err != nil { 83 return err 84 } 85 workRequestID := resp.OpcWorkRequestId 86 timeout := time.Duration(f.opt.CopyTimeout) 87 dstName := dstObj.String() 88 // https://docs.oracle.com/en-us/iaas/Content/Object/Tasks/copyingobjects.htm 89 // To enable server side copy object, customers will have to 90 // grant policy to objectstorage service to manage object-family 91 // Allow service objectstorage-<region_identifier> to manage object-family in tenancy 92 // Another option to avoid the policy is to download and reupload the file. 93 // This download upload will work for maximum file size limit of 5GB 94 err = copyObjectWaitForWorkRequest(ctx, workRequestID, dstName, timeout, f.srv) 95 if err != nil { 96 return err 97 } 98 return err 99 } 100 101 func copyObjectWaitForWorkRequest(ctx context.Context, wID *string, entityType string, timeout time.Duration, 102 client *objectstorage.ObjectStorageClient) error { 103 104 stateConf := &StateChangeConf{ 105 Pending: []string{ 106 string(objectstorage.WorkRequestStatusAccepted), 107 string(objectstorage.WorkRequestStatusInProgress), 108 string(objectstorage.WorkRequestStatusCanceling), 109 }, 110 Target: []string{ 111 string(objectstorage.WorkRequestSummaryStatusCompleted), 112 string(objectstorage.WorkRequestSummaryStatusCanceled), 113 string(objectstorage.WorkRequestStatusFailed), 114 }, 115 Refresh: func() (interface{}, string, error) { 116 getWorkRequestRequest := objectstorage.GetWorkRequestRequest{} 117 getWorkRequestRequest.WorkRequestId = wID 118 workRequestResponse, err := client.GetWorkRequest(context.Background(), getWorkRequestRequest) 119 wr := &workRequestResponse.WorkRequest 120 return workRequestResponse, string(wr.Status), err 121 }, 122 Timeout: timeout, 123 } 124 125 wrr, e := stateConf.WaitForStateContext(ctx, entityType) 126 if e != nil { 127 return fmt.Errorf("work request did not succeed, workId: %s, entity: %s. Message: %s", *wID, entityType, e) 128 } 129 130 wr := wrr.(objectstorage.GetWorkRequestResponse).WorkRequest 131 if wr.Status == objectstorage.WorkRequestStatusFailed { 132 errorMessage, _ := getObjectStorageErrorFromWorkRequest(ctx, wID, client) 133 return fmt.Errorf("work request did not succeed, workId: %s, entity: %s. Message: %s", *wID, entityType, errorMessage) 134 } 135 136 return nil 137 } 138 139 func getObjectStorageErrorFromWorkRequest(ctx context.Context, workRequestID *string, client *objectstorage.ObjectStorageClient) (string, error) { 140 req := objectstorage.ListWorkRequestErrorsRequest{} 141 req.WorkRequestId = workRequestID 142 res, err := client.ListWorkRequestErrors(ctx, req) 143 144 if err != nil { 145 return "", err 146 } 147 148 allErrs := make([]string, 0) 149 for _, errs := range res.Items { 150 allErrs = append(allErrs, *errs.Message) 151 } 152 153 errorMessage := strings.Join(allErrs, "\n") 154 return errorMessage, nil 155 }