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 }