mirror of
				https://github.com/minio/minio.git
				synced 2025-10-29 15:55:00 -04:00 
			
		
		
		
	multipart: remove proper MD5, rather create MD5 based on parts to be s3 compatible.
This increases the performance phenominally.
This commit is contained in:
		
							parent
							
								
									5f2cfdfbe2
								
							
						
					
					
						commit
						f111997184
					
				| @ -218,6 +218,11 @@ type CompleteMultipartUploadResponse struct { | ||||
| 	ETag     string | ||||
| } | ||||
| 
 | ||||
| // getLocation get URL location. | ||||
| func getLocation(r *http.Request) string { | ||||
| 	return r.URL.Path | ||||
| } | ||||
| 
 | ||||
| // takes an array of Bucketmetadata information for serialization | ||||
| // input: | ||||
| // array of bucket metadata | ||||
|  | ||||
| @ -291,7 +291,7 @@ func (api storageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 		return | ||||
| 	} | ||||
| 	// Make sure to add Location information here only for bucket | ||||
| 	w.Header().Set("Location", "/"+bucket) | ||||
| 	w.Header().Set("Location", getLocation(r)) | ||||
| 	writeSuccessResponse(w, nil) | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -798,7 +798,10 @@ func (api storageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, r *h | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	response := generateCompleteMultpartUploadResponse(bucket, object, r.URL.String(), metadata.MD5) | ||||
| 	// get object location. | ||||
| 	location := getLocation(r) | ||||
| 	// Generate complete multipart response. | ||||
| 	response := generateCompleteMultpartUploadResponse(bucket, object, location, metadata.MD5) | ||||
| 	encodedSuccessResponse := encodeSuccessResponse(response) | ||||
| 	// write headers | ||||
| 	setCommonHeaders(w) | ||||
|  | ||||
| @ -66,7 +66,6 @@ func testMultipartObjectCreation(c *check.C, create func() Filesystem) { | ||||
| 
 | ||||
| 	completedParts := CompleteMultipartUpload{} | ||||
| 	completedParts.Part = make([]CompletePart, 0) | ||||
| 	finalHasher := md5.New() | ||||
| 	for i := 1; i <= 10; i++ { | ||||
| 		randomPerm := rand.Perm(10) | ||||
| 		randomString := "" | ||||
| @ -75,7 +74,6 @@ func testMultipartObjectCreation(c *check.C, create func() Filesystem) { | ||||
| 		} | ||||
| 
 | ||||
| 		hasher := md5.New() | ||||
| 		finalHasher.Write([]byte(randomString)) | ||||
| 		hasher.Write([]byte(randomString)) | ||||
| 		expectedmd5Sum := base64.StdEncoding.EncodeToString(hasher.Sum(nil)) | ||||
| 		expectedmd5Sumhex := hex.EncodeToString(hasher.Sum(nil)) | ||||
| @ -87,12 +85,11 @@ func testMultipartObjectCreation(c *check.C, create func() Filesystem) { | ||||
| 		c.Assert(calculatedmd5sum, check.Equals, expectedmd5Sumhex) | ||||
| 		completedParts.Part = append(completedParts.Part, CompletePart{PartNumber: i, ETag: calculatedmd5sum}) | ||||
| 	} | ||||
| 	finalExpectedmd5SumHex := hex.EncodeToString(finalHasher.Sum(nil)) | ||||
| 	completedPartsBytes, e := xml.Marshal(completedParts) | ||||
| 	c.Assert(e, check.IsNil) | ||||
| 	objectMetadata, err := fs.CompleteMultipartUpload("bucket", "key", uploadID, bytes.NewReader(completedPartsBytes), nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(objectMetadata.MD5, check.Equals, finalExpectedmd5SumHex) | ||||
| 	c.Assert(objectMetadata.MD5, check.Equals, "9b7d6f13ba00e24d0b02de92e814891b-10") | ||||
| } | ||||
| 
 | ||||
| func testMultipartObjectAbort(c *check.C, create func() Filesystem) { | ||||
|  | ||||
| @ -65,7 +65,6 @@ func testMultipartObjectCreation(c *check.C, create func() Filesystem) { | ||||
| 
 | ||||
| 	completedParts := CompleteMultipartUpload{} | ||||
| 	completedParts.Part = make([]CompletePart, 0) | ||||
| 	finalHasher := md5.New() | ||||
| 	for i := 1; i <= 10; i++ { | ||||
| 		randomPerm := rand.Perm(10) | ||||
| 		randomString := "" | ||||
| @ -74,7 +73,6 @@ func testMultipartObjectCreation(c *check.C, create func() Filesystem) { | ||||
| 		} | ||||
| 
 | ||||
| 		hasher := md5.New() | ||||
| 		finalHasher.Write([]byte(randomString)) | ||||
| 		hasher.Write([]byte(randomString)) | ||||
| 		expectedmd5Sum := base64.StdEncoding.EncodeToString(hasher.Sum(nil)) | ||||
| 		expectedmd5Sumhex := hex.EncodeToString(hasher.Sum(nil)) | ||||
| @ -86,12 +84,11 @@ func testMultipartObjectCreation(c *check.C, create func() Filesystem) { | ||||
| 		c.Assert(calculatedmd5sum, check.Equals, expectedmd5Sumhex) | ||||
| 		completedParts.Part = append(completedParts.Part, CompletePart{PartNumber: i, ETag: calculatedmd5sum}) | ||||
| 	} | ||||
| 	finalExpectedmd5SumHex := hex.EncodeToString(finalHasher.Sum(nil)) | ||||
| 	completedPartsBytes, e := xml.Marshal(completedParts) | ||||
| 	c.Assert(e, check.IsNil) | ||||
| 	objectMetadata, err := fs.CompleteMultipartUpload("bucket", "key", uploadID, bytes.NewReader(completedPartsBytes), nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(objectMetadata.MD5, check.Equals, finalExpectedmd5SumHex) | ||||
| 	c.Assert(objectMetadata.MD5, check.Equals, "9b7d6f13ba00e24d0b02de92e814891b-10") | ||||
| } | ||||
| 
 | ||||
| func testMultipartObjectAbort(c *check.C, create func() Filesystem) { | ||||
|  | ||||
| @ -141,6 +141,22 @@ func doPartsMatch(parts []CompletePart, savedParts []PartMetadata) bool { | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // Create an s3 compatible MD5sum for complete multipart transaction. | ||||
| func makeS3MD5(md5Strs ...string) (string, *probe.Error) { | ||||
| 	var finalMD5Bytes []byte | ||||
| 	for _, md5Str := range md5Strs { | ||||
| 		md5Bytes, e := hex.DecodeString(md5Str) | ||||
| 		if e != nil { | ||||
| 			return "", probe.NewError(e) | ||||
| 		} | ||||
| 		finalMD5Bytes = append(finalMD5Bytes, md5Bytes...) | ||||
| 	} | ||||
| 	md5Hasher := md5.New() | ||||
| 	md5Hasher.Write(finalMD5Bytes) | ||||
| 	s3MD5 := fmt.Sprintf("%s-%d", hex.EncodeToString(md5Hasher.Sum(nil)), len(md5Strs)) | ||||
| 	return s3MD5, nil | ||||
| } | ||||
| 
 | ||||
| type multiCloser struct { | ||||
| 	Closers []io.Closer | ||||
| } | ||||
| @ -177,7 +193,9 @@ func saveParts(partPathPrefix string, mw io.Writer, parts []CompletePart) *probe | ||||
| 	var partReaders []io.Reader | ||||
| 	var partClosers []io.Closer | ||||
| 	for _, part := range parts { | ||||
| 		// Trim prefix | ||||
| 		md5Sum := strings.TrimPrefix(part.ETag, "\"") | ||||
| 		// Trim suffix | ||||
| 		md5Sum = strings.TrimSuffix(md5Sum, "\"") | ||||
| 		partFile, e := os.OpenFile(partPathPrefix+md5Sum+fmt.Sprintf("$%d-$multiparts", part.PartNumber), os.O_RDONLY, 0600) | ||||
| 		if e != nil { | ||||
| @ -199,7 +217,8 @@ func saveParts(partPathPrefix string, mw io.Writer, parts []CompletePart) *probe | ||||
| 	defer closer.Close() | ||||
| 
 | ||||
| 	reader := io.MultiReader(partReaders...) | ||||
| 	readBuffer := make([]byte, 4*1024*1024) | ||||
| 	readBufferSize := 8 * 1024 * 1024          // 8MiB | ||||
| 	readBuffer := make([]byte, readBufferSize) // Allocate 8MiB buffer. | ||||
| 	if _, e := io.CopyBuffer(mw, reader, readBuffer); e != nil { | ||||
| 		return probe.NewError(e) | ||||
| 	} | ||||
| @ -465,16 +484,14 @@ func (fs Filesystem) CompleteMultipartUpload(bucket, object, uploadID string, da | ||||
| 	} | ||||
| 
 | ||||
| 	objectPath := filepath.Join(bucketPath, object) | ||||
| 	file, e := atomic.FileCreateWithPrefix(objectPath, "$tmpobject") | ||||
| 	objectWriter, e := atomic.FileCreateWithPrefix(objectPath, "$tmpobject") | ||||
| 	if e != nil { | ||||
| 		return ObjectMetadata{}, probe.NewError(e) | ||||
| 	} | ||||
| 	md5Hasher := md5.New() | ||||
| 	objectWriter := io.MultiWriter(file, md5Hasher) | ||||
| 
 | ||||
| 	partBytes, e := ioutil.ReadAll(data) | ||||
| 	if e != nil { | ||||
| 		file.CloseAndPurge() | ||||
| 		objectWriter.CloseAndPurge() | ||||
| 		return ObjectMetadata{}, probe.NewError(e) | ||||
| 	} | ||||
| 	if signature != nil { | ||||
| @ -482,21 +499,21 @@ func (fs Filesystem) CompleteMultipartUpload(bucket, object, uploadID string, da | ||||
| 		sh.Write(partBytes) | ||||
| 		ok, err := signature.DoesSignatureMatch(hex.EncodeToString(sh.Sum(nil))) | ||||
| 		if err != nil { | ||||
| 			file.CloseAndPurge() | ||||
| 			objectWriter.CloseAndPurge() | ||||
| 			return ObjectMetadata{}, err.Trace() | ||||
| 		} | ||||
| 		if !ok { | ||||
| 			file.CloseAndPurge() | ||||
| 			objectWriter.CloseAndPurge() | ||||
| 			return ObjectMetadata{}, probe.NewError(SignDoesNotMatch{}) | ||||
| 		} | ||||
| 	} | ||||
| 	completeMultipartUpload := &CompleteMultipartUpload{} | ||||
| 	if e = xml.Unmarshal(partBytes, completeMultipartUpload); e != nil { | ||||
| 		file.CloseAndPurge() | ||||
| 		objectWriter.CloseAndPurge() | ||||
| 		return ObjectMetadata{}, probe.NewError(MalformedXML{}) | ||||
| 	} | ||||
| 	if !sort.IsSorted(completedParts(completeMultipartUpload.Part)) { | ||||
| 		file.CloseAndPurge() | ||||
| 		objectWriter.CloseAndPurge() | ||||
| 		return ObjectMetadata{}, probe.NewError(InvalidPartOrder{}) | ||||
| 	} | ||||
| 
 | ||||
| @ -509,28 +526,42 @@ func (fs Filesystem) CompleteMultipartUpload(bucket, object, uploadID string, da | ||||
| 	fs.rwLock.RUnlock() | ||||
| 
 | ||||
| 	if !doPartsMatch(parts, savedParts) { | ||||
| 		file.CloseAndPurge() | ||||
| 		objectWriter.CloseAndPurge() | ||||
| 		return ObjectMetadata{}, probe.NewError(InvalidPart{}) | ||||
| 	} | ||||
| 
 | ||||
| 	// Parts successfully validated, save all the parts. | ||||
| 	partPathPrefix := objectPath + uploadID | ||||
| 	if err := saveParts(partPathPrefix, objectWriter, parts); err != nil { | ||||
| 		file.CloseAndPurge() | ||||
| 		objectWriter.CloseAndPurge() | ||||
| 		return ObjectMetadata{}, err.Trace(partPathPrefix) | ||||
| 	} | ||||
| 	// Successfully saved, remove all parts. | ||||
| 	removeParts(partPathPrefix, savedParts) | ||||
| 	var md5Strs []string | ||||
| 	for _, part := range savedParts { | ||||
| 		md5Strs = append(md5Strs, part.ETag) | ||||
| 	} | ||||
| 	// Save the s3 md5. | ||||
| 	s3MD5, err := makeS3MD5(md5Strs...) | ||||
| 	if err != nil { | ||||
| 		objectWriter.CloseAndPurge() | ||||
| 		return ObjectMetadata{}, err.Trace(md5Strs...) | ||||
| 	} | ||||
| 
 | ||||
| 	// Successfully saved multipart, remove all parts in a routine. | ||||
| 	go removeParts(partPathPrefix, savedParts) | ||||
| 
 | ||||
| 	// Critical region requiring write lock. | ||||
| 	fs.rwLock.Lock() | ||||
| 	delete(fs.multiparts.ActiveSession, uploadID) | ||||
| 	if err := saveMultipartsSession(*fs.multiparts); err != nil { | ||||
| 		fs.rwLock.Unlock() | ||||
| 		file.CloseAndPurge() | ||||
| 		objectWriter.CloseAndPurge() | ||||
| 		return ObjectMetadata{}, err.Trace(partPathPrefix) | ||||
| 	} | ||||
| 	file.Close() | ||||
| 	if e = objectWriter.Close(); e != nil { | ||||
| 		fs.rwLock.Unlock() | ||||
| 		return ObjectMetadata{}, probe.NewError(e) | ||||
| 	} | ||||
| 	fs.rwLock.Unlock() | ||||
| 
 | ||||
| 	// Send stat again to get object metadata. | ||||
| @ -538,6 +569,7 @@ func (fs Filesystem) CompleteMultipartUpload(bucket, object, uploadID string, da | ||||
| 	if e != nil { | ||||
| 		return ObjectMetadata{}, probe.NewError(e) | ||||
| 	} | ||||
| 
 | ||||
| 	contentType := "application/octet-stream" | ||||
| 	if objectExt := filepath.Ext(objectPath); objectExt != "" { | ||||
| 		content, ok := mimedb.DB[strings.ToLower(strings.TrimPrefix(objectExt, "."))] | ||||
| @ -551,7 +583,7 @@ func (fs Filesystem) CompleteMultipartUpload(bucket, object, uploadID string, da | ||||
| 		LastModified: st.ModTime(), | ||||
| 		Size:         st.Size(), | ||||
| 		ContentType:  contentType, | ||||
| 		MD5:          hex.EncodeToString(md5Hasher.Sum(nil)), | ||||
| 		MD5:          s3MD5, | ||||
| 	} | ||||
| 	return newObject, nil | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user