@@ -23,7 +23,6 @@ import (
2323 "net/http"
2424 "net/url"
2525 "os"
26- "sort"
2726 "strconv"
2827
2928 "github.com/NVIDIA/aistore/ais/s3"
@@ -134,7 +133,6 @@ func (t *target) putMptPart(w http.ResponseWriter, r *http.Request, items []stri
134133 var (
135134 etag string
136135 expectedSize int64
137- ecode int
138136 partSHA = r .Header .Get (cos .S3HdrContentSHA256 )
139137 checkPartSHA = partSHA != "" && partSHA != cos .S3UnsignedPayload
140138 cksumSHA * cos.CksumHash
@@ -150,15 +148,17 @@ func (t *target) putMptPart(w http.ResponseWriter, r *http.Request, items []stri
150148
151149 if r .ContentLength <= 0 {
152150 err := fmt .Errorf ("put-part invalid size (%d)" , r .ContentLength )
153- s3 .WriteMptErr (w , r , err , ecode , lom , uploadID )
151+ s3 .WriteMptErr (w , r , err , 0 , lom , uploadID )
154152 return
155153 }
156154
157155 // 3. write
158156 // for remote buckets, use SGL buffering when memory is available;
159157 // fall back to TeeReader to avoid high memory usage under pressure
160- mw := cos .IniWriterMulti (cksumMD5 .H , cksumSHA .H , partFh )
161-
158+ var (
159+ ecode int
160+ mw = cos .IniWriterMulti (cksumMD5 .H , cksumSHA .H , partFh )
161+ )
162162 switch {
163163 case ! remote :
164164 buf , slab := t .gmm .AllocSize (r .ContentLength )
@@ -183,7 +183,6 @@ func (t *target) putMptPart(w http.ResponseWriter, r *http.Request, items []stri
183183 etag , ecode , err = t .ups .putPartRemote (lom , tr , r , q , uploadID , expectedSize , partNum )
184184 remotePutLatency = mono .SinceNano (remoteStart )
185185 }
186-
187186 cos .Close (partFh )
188187 if err != nil {
189188 if nerr := cos .RemoveFile (chunkPath ); nerr != nil && ! cos .IsNotExist (nerr ) {
@@ -196,7 +195,7 @@ func (t *target) putMptPart(w http.ResponseWriter, r *http.Request, items []stri
196195 size := mw .Size ()
197196 if size != expectedSize {
198197 err := fmt .Errorf ("part %d size mismatch (%d vs %d)" , partNum , size , expectedSize )
199- s3 .WriteMptErr (w , r , err , ecode , lom , uploadID )
198+ s3 .WriteMptErr (w , r , err , 0 , lom , uploadID )
200199 return
201200 }
202201
@@ -281,100 +280,36 @@ func (t *target) completeMpt(w http.ResponseWriter, r *http.Request, items []str
281280 s3 .WriteErr (w , r , err , http .StatusBadRequest )
282281 return
283282 }
284- if len (partList .Parts ) == 0 {
285- s3 .WriteErr (w , r , fmt . Errorf ( "upload %q: empty list of upload parts", uploadID ), http .StatusBadRequest )
283+ if partList == nil || len (partList .Parts ) == 0 {
284+ s3 .WriteErr (w , r , errors . New ( "no parts" ), http .StatusBadRequest )
286285 return
287286 }
287+
288288 objName := s3 .ObjName (items )
289289 lom := & core.LOM {ObjName : objName } // TODO: use core.AllocLOM()
290290 if err := lom .InitBck (bck .Bucket ()); err != nil {
291291 s3 .WriteErr (w , r , err , 0 )
292292 return
293293 }
294294
295- // TODO: compare with listMptUploads (that does fromFS )
295+ // ( compare with listMptUploads)
296296 manifest , metadata := t .ups .getWithMeta (uploadID )
297297 if manifest == nil {
298298 s3 .WriteMptErr (w , r , s3 .NewErrNoSuchUpload (uploadID , nil ), http .StatusNotFound , lom , uploadID )
299299 return
300300 }
301301
302- // validate parts (note: maybe too strict)
303- var (
304- numParts = len (partList .Parts )
305- )
306- if numParts == 0 {
307- s3 .WriteMptErr (w , r , errors .New ("empty parts list" ), 0 , lom , uploadID )
308- return
309- }
310- sort .Slice (partList .Parts , func (i , j int ) bool {
311- return * partList .Parts [i ].PartNumber < * partList .Parts [j ].PartNumber
312- })
313- for i , p := range partList .Parts {
314- if p .PartNumber == nil {
315- s3 .WriteMptErr (w , r , fmt .Errorf ("nil part number at index %d" , i ), http .StatusBadRequest , lom , uploadID )
316- return
317- }
318- want := int32 (i + 1 )
319- got := * p .PartNumber
320- if got != want {
321- s3 .WriteMptErr (w , r ,
322- fmt .Errorf ("parts must be strictly ascending and contiguous (1..N): got %d at position %d" , got , i ),
323- http .StatusBadRequest , lom , uploadID )
324- return
325- }
326- }
327-
328- manifest .Lock () // ------------------------------------------------ TODO -- FIXME: consider CoW
329- if len (manifest .Chunks ) < numParts {
330- manifest .Unlock ()
331- s3 .WriteMptErr (w , r ,
332- fmt .Errorf ("have %d parts, requested %d" , len (manifest .Chunks ), numParts ),
333- http .StatusBadRequest , lom , uploadID )
334- return
335- }
336- nparts := make ([]* core.Uchunk , numParts )
337- for i := range numParts {
338- c := & manifest .Chunks [i ]
339- if c .Num () != uint16 (i + 1 ) {
340- manifest .Unlock ()
341- s3 .WriteMptErr (w , r , fmt .Errorf ("missing or out-of-order part %d (found %d)" , i + 1 , c .Num ()), 0 , lom , uploadID )
342- return
343- }
344- nparts [i ] = c
345- }
346-
347- // compute "whole" checksum // TODO -- FIXME: sha256 may take precedence when implied by partSHA (see above)
348- var (
349- wholeCksum * cos.CksumHash
350- remote = bck .IsRemoteS3 () || bck .IsRemoteOCI ()
351- )
352- if remote && lom .CksumConf ().Type != cos .ChecksumNone {
353- wholeCksum = cos .NewCksumHash (lom .CksumConf ().Type )
354- } else {
355- wholeCksum = cos .NewCksumHash (cos .ChecksumMD5 )
356- }
357-
358- if wholeCksum != nil {
359- if err := manifest .ComputeWholeChecksum (wholeCksum ); err != nil {
360- s3 .WriteMptErr (w , r , err , 0 , lom , uploadID )
361- return
362- }
363- lom .SetCksum (& wholeCksum .Cksum )
364- }
365-
366- // compute multipart-compliant ETag if need be
302+ // validate/enforce parts, compute _whole" checksum and etag
367303 var etag string
368- if ! remote {
369- var err error
370- if etag , err = manifest .ETagS3 (); err != nil {
371- s3 .WriteMptErr (w , r , err , 0 , lom , uploadID )
372- return
373- }
304+ manifest .Lock ()
305+ etag , err = validateChecksumEtag (w , r , lom , manifest , partList )
306+ manifest .Unlock ()
307+ if err != nil {
308+ return
374309 }
375- manifest .Unlock () // -------------------------------------------------- TODO -- FIXME: ditto
376310
377311 // call remote
312+ remote := bck .IsRemoteS3 () || bck .IsRemoteOCI ()
378313 if remote {
379314 var err error
380315 etag , err = t .ups .completeRemote (w , r , lom , q , uploadID , body , partList )
@@ -422,6 +357,50 @@ func (t *target) completeMpt(w http.ResponseWriter, r *http.Request, items []str
422357 }
423358}
424359
360+ // (under manifest lock)
361+ func validateChecksumEtag (w http.ResponseWriter , r * http.Request , lom * core.LOM , manifest * core.Ufest , partList * s3.CompleteMptUpload ) (string , error ) {
362+ uploadID := manifest .ID ()
363+ if err := manifest .Check (); err != nil {
364+ s3 .WriteMptErr (w , r , err , 0 , lom , uploadID )
365+ return "" , err
366+ }
367+ if ecode , err := s3 .EnforceCompleteAllParts (partList , manifest .Count ()); err != nil {
368+ s3 .WriteMptErr (w , r , err , ecode , lom , uploadID )
369+ return "" , err
370+ }
371+
372+ // compute "whole" checksum (TODO: sha256 may take precedence when implied by `partSHA`)
373+ var (
374+ wholeCksum * cos.CksumHash
375+ bck = lom .Bck ()
376+ remote = bck .IsRemoteS3 () || bck .IsRemoteOCI ()
377+ )
378+ if remote && lom .CksumConf ().Type != cos .ChecksumNone {
379+ wholeCksum = cos .NewCksumHash (lom .CksumConf ().Type )
380+ } else {
381+ wholeCksum = cos .NewCksumHash (cos .ChecksumMD5 )
382+ }
383+ if wholeCksum != nil {
384+ if err := manifest .ComputeWholeChecksum (wholeCksum ); err != nil {
385+ s3 .WriteMptErr (w , r , err , 0 , lom , uploadID )
386+ return "" , err
387+ }
388+ lom .SetCksum (& wholeCksum .Cksum )
389+ }
390+
391+ // compute multipart-compliant ETag if need be
392+ var etag string
393+ if ! remote {
394+ var err error
395+ if etag , err = manifest .ETagS3 (); err != nil {
396+ s3 .WriteMptErr (w , r , err , 0 , lom , uploadID )
397+ return "" , err
398+ }
399+ }
400+
401+ return etag , nil
402+ }
403+
425404// Abort an active multipart upload.
426405// Body is empty, only URL query contains uploadID
427406// 1. uploadID must exists
@@ -548,17 +527,17 @@ func (t *target) getMptPart(w http.ResponseWriter, r *http.Request, bck *meta.Bc
548527 return
549528 }
550529
551- // Find the specific chunk
530+ // get specific chunk
552531 manifest .Lock ()
553- chunk := manifest .GetChunk (uint16 (partNum ), true )
532+ chunk := manifest .GetChunk (int (partNum ), true )
554533 manifest .Unlock ()
555534 if chunk == nil {
556535 err := fmt .Errorf ("part %d not found" , partNum )
557536 s3 .WriteErr (w , r , err , http .StatusNotFound )
558537 return
559538 }
560539
561- // Read chunk file directly
540+ // read chunk file
562541 fh , err := os .Open (chunk .Path )
563542 if err != nil {
564543 s3 .WriteErr (w , r , err , 0 )
0 commit comments