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  }