storj.io/uplink@v1.13.0/private/storage/streams/streamupload/upload.go (about)

     1  // Copyright (C) 2023 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package streamupload
     5  
     6  import (
     7  	"context"
     8  
     9  	"github.com/spacemonkeygo/monkit/v3"
    10  	"github.com/zeebo/errs"
    11  
    12  	"storj.io/common/context2"
    13  	"storj.io/common/errs2"
    14  	"storj.io/common/pb"
    15  	"storj.io/common/storj"
    16  	"storj.io/uplink/private/metaclient"
    17  	"storj.io/uplink/private/storage/streams/batchaggregator"
    18  	"storj.io/uplink/private/storage/streams/segmenttracker"
    19  	"storj.io/uplink/private/storage/streams/splitter"
    20  	"storj.io/uplink/private/storage/streams/streambatcher"
    21  	"storj.io/uplink/private/testuplink"
    22  )
    23  
    24  var mon = monkit.Package()
    25  
    26  // SegmentSource is a source of segments to be uploaded.
    27  type SegmentSource interface {
    28  	// Next returns the next segment. It will return all-nil when there are no
    29  	// more segments left.
    30  	Next(context.Context) (splitter.Segment, error)
    31  }
    32  
    33  // SegmentUploader uploads a single remote segment of the stream.
    34  type SegmentUploader interface {
    35  	// Begin starts an Upload for a single remote segment of the stream.
    36  	// Callers can wait for the upload to finish using the Wait() method.
    37  	Begin(ctx context.Context, resp *metaclient.BeginSegmentResponse, segment splitter.Segment) (SegmentUpload, error)
    38  }
    39  
    40  // SegmentUpload is an upload for a single remote segment of the stream.
    41  type SegmentUpload interface {
    42  	// Wait waits until the segment is uploaded and returns the request needed
    43  	// to commit the segment to the metainfo store.
    44  	Wait() (*metaclient.CommitSegmentParams, error)
    45  }
    46  
    47  // EncryptedMetadata is used to encrypt the metadata from the object.
    48  type EncryptedMetadata interface {
    49  	// EncryptedMetadata creates stream metadata, including the size of the
    50  	// final segment. The stream metadata is encrypted and returned. Also
    51  	// returned is an encrypted version of the key used to encrypt the metadata
    52  	// as well as the nonce used to encrypt the key.
    53  	EncryptedMetadata(lastSegmentSize int64) (data []byte, encKey *storj.EncryptedPrivateKey, nonce *storj.Nonce, err error)
    54  }
    55  
    56  // Info is the information about the stream upload.
    57  type Info = streambatcher.Info
    58  
    59  // UploadObject uploads a stream of segments as an object identified by the
    60  // given beginObject response.
    61  func UploadObject(ctx context.Context, segmentSource SegmentSource, segmentUploader SegmentUploader, miBatcher metaclient.Batcher, beginObject *metaclient.BeginObjectParams, encMeta EncryptedMetadata) (_ Info, err error) {
    62  	defer mon.Task()(&ctx)(&err)
    63  	return uploadSegments(ctx, segmentSource, segmentUploader, miBatcher, beginObject, encMeta, nil, nil)
    64  }
    65  
    66  // UploadPart uploads a stream of segments as a part of a multipart upload
    67  // identified by the given streamID.
    68  func UploadPart(ctx context.Context, segmentSource SegmentSource, segmentUploader SegmentUploader, miBatcher metaclient.Batcher, streamID storj.StreamID, eTagCh <-chan []byte) (_ Info, err error) {
    69  	defer mon.Task()(&ctx)(&err)
    70  	return uploadSegments(ctx, segmentSource, segmentUploader, miBatcher, nil, nil, streamID, eTagCh)
    71  }
    72  
    73  func uploadSegments(ctx context.Context, segmentSource SegmentSource, segmentUploader SegmentUploader, miBatcher metaclient.Batcher, beginObject *metaclient.BeginObjectParams, encMeta EncryptedMetadata, streamID storj.StreamID, eTagCh <-chan []byte) (_ Info, err error) {
    74  	defer mon.Task()(&ctx)(&err)
    75  
    76  	testuplink.Log(ctx, "Uploading segments...")
    77  	defer testuplink.Log(ctx, "Done uploading segments...")
    78  
    79  	batcher := streambatcher.New(miBatcher, streamID)
    80  	aggregator := batchaggregator.New(batcher)
    81  
    82  	if beginObject != nil {
    83  		aggregator.Schedule(beginObject)
    84  		defer func() {
    85  			if err != nil {
    86  				if batcherStreamID := batcher.StreamID(); !batcherStreamID.IsZero() {
    87  					if deleteErr := deleteCancelledObject(ctx, miBatcher, beginObject, batcherStreamID); deleteErr != nil {
    88  						mon.Event("failed to delete cancelled object")
    89  					}
    90  				}
    91  			}
    92  		}()
    93  	}
    94  
    95  	tracker := segmenttracker.New(aggregator, eTagCh)
    96  
    97  	var segments []splitter.Segment
    98  	defer func() {
    99  		for _, segment := range segments {
   100  			segment.DoneReading(err)
   101  		}
   102  	}()
   103  
   104  	uploadCtx := ctx
   105  	ctx, cg := newCancelGroup(ctx)
   106  	defer cg.Close()
   107  
   108  	for {
   109  		segment, err := segmentSource.Next(ctx)
   110  		if err != nil {
   111  			// If next returns "canceled" it is because either:
   112  			// 1) the upload itself is being canceled
   113  			// 2) a goroutine run by the "cancel group" returned an error
   114  			//
   115  			// Check to see if the upload context is cancelled, and if not
   116  			// assume a goroutine failed and return the error from the cancel
   117  			// group.
   118  			if errs2.IsCanceled(err) && uploadCtx.Err() == nil {
   119  				err = cg.Wait()
   120  			}
   121  			testuplink.Log(ctx, "Next segment err:", err)
   122  			return Info{}, err
   123  		} else if segment == nil {
   124  			testuplink.Log(ctx, "Next returned nil segment")
   125  			break
   126  		}
   127  		segments = append(segments, segment)
   128  		testuplink.Log(ctx, "Got next segment. Inline:", segment.Inline())
   129  
   130  		if segment.Inline() {
   131  			tracker.SegmentDone(segment, segment.Begin())
   132  			break
   133  		}
   134  
   135  		cg.Go(func() error {
   136  			resp, err := aggregator.ScheduleAndFlush(ctx, segment.Begin())
   137  			if err != nil {
   138  				return err
   139  			}
   140  
   141  			beginSegment, err := resp.BeginSegment()
   142  			if err != nil {
   143  				return err
   144  			}
   145  
   146  			upload, err := segmentUploader.Begin(ctx, &beginSegment, segment)
   147  			if err != nil {
   148  				return err
   149  			}
   150  
   151  			commitSegment, err := upload.Wait()
   152  			if err != nil {
   153  				return err
   154  			}
   155  			tracker.SegmentDone(segment, commitSegment)
   156  			return nil
   157  		})
   158  	}
   159  
   160  	if len(segments) == 0 {
   161  		return Info{}, errs.New("programmer error: there should always be at least one segment")
   162  	}
   163  
   164  	lastSegment := segments[len(segments)-1]
   165  
   166  	tracker.SegmentsScheduled(lastSegment)
   167  
   168  	testuplink.Log(ctx, "Waiting for error group managing segments...")
   169  	if err := cg.Wait(); err != nil {
   170  		return Info{}, err
   171  	}
   172  
   173  	if err := tracker.Flush(ctx); err != nil {
   174  		return Info{}, err
   175  	}
   176  
   177  	// we need to schedule a commit object if we had a begin object
   178  	if beginObject != nil {
   179  		commitObject, err := createCommitObjectParams(lastSegment, encMeta)
   180  		if err != nil {
   181  			return Info{}, err
   182  		}
   183  		aggregator.Schedule(commitObject)
   184  	}
   185  
   186  	if err := aggregator.Flush(ctx); err != nil {
   187  		return Info{}, err
   188  	}
   189  
   190  	return batcher.Info()
   191  }
   192  
   193  func createCommitObjectParams(lastSegment splitter.Segment, encMeta EncryptedMetadata) (*metaclient.CommitObjectParams, error) {
   194  	info := lastSegment.Finalize()
   195  
   196  	encryptedMetadata, encryptedMetadataKey, encryptedMetadataKeyNonce, err := encMeta.EncryptedMetadata(info.PlainSize)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	return &metaclient.CommitObjectParams{
   202  		StreamID:                      nil, // set by the stream batcher
   203  		EncryptedMetadataNonce:        *encryptedMetadataKeyNonce,
   204  		EncryptedMetadataEncryptedKey: *encryptedMetadataKey,
   205  		EncryptedMetadata:             encryptedMetadata,
   206  	}, nil
   207  }
   208  
   209  func deleteCancelledObject(ctx context.Context, batcher metaclient.Batcher, beginObject *metaclient.BeginObjectParams, streamID storj.StreamID) (err error) {
   210  	defer mon.Task()(&ctx)(&err)
   211  
   212  	ctx = context2.WithoutCancellation(ctx)
   213  	_, err = batcher.Batch(ctx, &metaclient.BeginDeleteObjectParams{
   214  		Bucket:             beginObject.Bucket,
   215  		EncryptedObjectKey: beginObject.EncryptedObjectKey,
   216  		StreamID:           streamID,
   217  		Status:             int32(pb.Object_UPLOADING),
   218  	})
   219  	return err
   220  }