mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
objects: Save all the incoming metadata properly. (#1688)
For both multipart and single put operation
This commit is contained in:
parent
af85acf388
commit
7d6ed50fc2
@ -28,8 +28,9 @@ func (fs fsObjects) ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMark
|
||||
}
|
||||
|
||||
// NewMultipartUpload - initialize a new multipart upload, returns a unique id.
|
||||
func (fs fsObjects) NewMultipartUpload(bucket, object string) (string, error) {
|
||||
return newMultipartUploadCommon(fs.storage, bucket, object)
|
||||
func (fs fsObjects) NewMultipartUpload(bucket, object string, meta map[string]string) (string, error) {
|
||||
meta = make(map[string]string) // Reset the meta value, we are not going to save headers for fs.
|
||||
return newMultipartUploadCommon(fs.storage, bucket, object, meta)
|
||||
}
|
||||
|
||||
// PutObjectPart - writes the multipart upload chunks.
|
||||
|
@ -35,7 +35,7 @@ func testObjectNewMultipartUpload(obj ObjectLayer, instanceType string, t *testi
|
||||
|
||||
errMsg := "Bucket not found: minio-bucket"
|
||||
// opearation expected to fail since the bucket on which NewMultipartUpload is being initiated doesn't exist.
|
||||
uploadID, err := obj.NewMultipartUpload(bucket, object)
|
||||
uploadID, err := obj.NewMultipartUpload(bucket, object, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("%s: Expected to fail since the NewMultipartUpload is intialized on a non-existant bucket.", instanceType)
|
||||
}
|
||||
@ -50,7 +50,7 @@ func testObjectNewMultipartUpload(obj ObjectLayer, instanceType string, t *testi
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
|
||||
uploadID, err = obj.NewMultipartUpload(bucket, object)
|
||||
uploadID, err = obj.NewMultipartUpload(bucket, object, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
@ -83,7 +83,7 @@ func testObjectAPIIsUploadIDExists(obj ObjectLayer, instanceType string, t *test
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
|
||||
_, err = obj.NewMultipartUpload(bucket, object)
|
||||
_, err = obj.NewMultipartUpload(bucket, object, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
@ -114,7 +114,7 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t *testing
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
// Initiate Multipart Upload on the above created bucket.
|
||||
uploadID, err := obj.NewMultipartUpload(bucket, object)
|
||||
uploadID, err := obj.NewMultipartUpload(bucket, object, nil)
|
||||
if err != nil {
|
||||
// Failed to create NewMultipartUpload, abort.
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
|
@ -19,6 +19,7 @@ package main
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -71,7 +72,7 @@ func createUploadsJSON(storage StorageAPI, bucket, object, uploadID string) erro
|
||||
|
||||
// newMultipartUploadCommon - initialize a new multipart, is a common
|
||||
// function for both object layers.
|
||||
func newMultipartUploadCommon(storage StorageAPI, bucket string, object string) (uploadID string, err error) {
|
||||
func newMultipartUploadCommon(storage StorageAPI, bucket string, object string, meta map[string]string) (uploadID string, err error) {
|
||||
// Verify if bucket name is valid.
|
||||
if !IsValidBucketName(bucket) {
|
||||
return "", BucketNameInvalid{Bucket: bucket}
|
||||
@ -111,6 +112,17 @@ func newMultipartUploadCommon(storage StorageAPI, bucket string, object string)
|
||||
if w, err = storage.CreateFile(minioMetaBucket, tempUploadIDPath); err != nil {
|
||||
return "", toObjectErr(err, minioMetaBucket, tempUploadIDPath)
|
||||
}
|
||||
|
||||
// Encode the uploaded metadata into incomplete file.
|
||||
encoder := json.NewEncoder(w)
|
||||
err = encoder.Encode(&meta)
|
||||
if err != nil {
|
||||
if clErr := safeCloseAndRemove(w); clErr != nil {
|
||||
return "", toObjectErr(clErr, minioMetaBucket, tempUploadIDPath)
|
||||
}
|
||||
return "", toObjectErr(err, minioMetaBucket, tempUploadIDPath)
|
||||
}
|
||||
|
||||
// Close the writer.
|
||||
if err = w.Close(); err != nil {
|
||||
if clErr := safeCloseAndRemove(w); clErr != nil {
|
||||
@ -118,6 +130,8 @@ func newMultipartUploadCommon(storage StorageAPI, bucket string, object string)
|
||||
}
|
||||
return "", toObjectErr(err, minioMetaBucket, tempUploadIDPath)
|
||||
}
|
||||
|
||||
// Rename the file to the actual location from temporary path.
|
||||
err = storage.RenameFile(minioMetaBucket, tempUploadIDPath, minioMetaBucket, uploadIDPath)
|
||||
if err != nil {
|
||||
if derr := storage.DeleteFile(minioMetaBucket, tempUploadIDPath); derr != nil {
|
||||
|
@ -554,6 +554,22 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
// Save metadata.
|
||||
metadata := make(map[string]string)
|
||||
// Make sure we hex encode md5sum here.
|
||||
metadata["md5Sum"] = hex.EncodeToString(md5Bytes)
|
||||
// Save other metadata if available.
|
||||
metadata["content-type"] = r.Header.Get("Content-Type")
|
||||
metadata["content-encoding"] = r.Header.Get("Content-Encoding")
|
||||
for key := range r.Header {
|
||||
cKey := http.CanonicalHeaderKey(key)
|
||||
if strings.HasPrefix(cKey, "x-amz-meta-") {
|
||||
metadata[cKey] = r.Header.Get(cKey)
|
||||
} else if strings.HasPrefix(key, "x-minio-meta-") {
|
||||
metadata[cKey] = r.Header.Get(cKey)
|
||||
}
|
||||
}
|
||||
|
||||
var md5Sum string
|
||||
switch getRequestAuthType(r) {
|
||||
default:
|
||||
@ -567,7 +583,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
// Create anonymous object.
|
||||
md5Sum, err = api.ObjectAPI.PutObject(bucket, object, size, r.Body, nil)
|
||||
md5Sum, err = api.ObjectAPI.PutObject(bucket, object, size, r.Body, metadata)
|
||||
case authTypePresigned, authTypeSigned:
|
||||
// Initialize a pipe for data pipe line.
|
||||
reader, writer := io.Pipe()
|
||||
@ -608,10 +624,6 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
writer.Close()
|
||||
}()
|
||||
|
||||
// Save metadata.
|
||||
metadata := make(map[string]string)
|
||||
// Make sure we hex encode here.
|
||||
metadata["md5Sum"] = hex.EncodeToString(md5Bytes)
|
||||
// Create object.
|
||||
md5Sum, err = api.ObjectAPI.PutObject(bucket, object, size, reader, metadata)
|
||||
// Close the pipe.
|
||||
@ -657,7 +669,21 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
}
|
||||
}
|
||||
|
||||
uploadID, err := api.ObjectAPI.NewMultipartUpload(bucket, object)
|
||||
// Save metadata.
|
||||
metadata := make(map[string]string)
|
||||
// Save other metadata if available.
|
||||
metadata["content-type"] = r.Header.Get("Content-Type")
|
||||
metadata["content-encoding"] = r.Header.Get("Content-Encoding")
|
||||
for key := range r.Header {
|
||||
cKey := http.CanonicalHeaderKey(key)
|
||||
if strings.HasPrefix(cKey, "x-amz-meta-") {
|
||||
metadata[cKey] = r.Header.Get(cKey)
|
||||
} else if strings.HasPrefix(key, "x-minio-meta-") {
|
||||
metadata[cKey] = r.Header.Get(cKey)
|
||||
}
|
||||
}
|
||||
|
||||
uploadID, err := api.ObjectAPI.NewMultipartUpload(bucket, object, metadata)
|
||||
if err != nil {
|
||||
errorIf(err, "Unable to initiate new multipart upload id.")
|
||||
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
|
||||
|
@ -35,7 +35,7 @@ type ObjectLayer interface {
|
||||
|
||||
// Multipart operations.
|
||||
ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartsInfo, err error)
|
||||
NewMultipartUpload(bucket, object string) (uploadID string, err error)
|
||||
NewMultipartUpload(bucket, object string, metadata map[string]string) (uploadID string, err error)
|
||||
PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (md5 string, err error)
|
||||
ListObjectParts(bucket, object, uploadID string, partNumberMarker int, maxParts int) (result ListPartsInfo, err error)
|
||||
AbortMultipartUpload(bucket, object, uploadID string) error
|
||||
|
@ -61,7 +61,7 @@ func testMultipartObjectCreation(c *check.C, create func() ObjectLayer) {
|
||||
obj := create()
|
||||
err := obj.MakeBucket("bucket")
|
||||
c.Assert(err, check.IsNil)
|
||||
uploadID, err := obj.NewMultipartUpload("bucket", "key")
|
||||
uploadID, err := obj.NewMultipartUpload("bucket", "key", nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
// Create a byte array of 5MB.
|
||||
data := bytes.Repeat([]byte("0123456789abcdef"), 5*1024*1024/16)
|
||||
@ -87,7 +87,7 @@ func testMultipartObjectAbort(c *check.C, create func() ObjectLayer) {
|
||||
obj := create()
|
||||
err := obj.MakeBucket("bucket")
|
||||
c.Assert(err, check.IsNil)
|
||||
uploadID, err := obj.NewMultipartUpload("bucket", "key")
|
||||
uploadID, err := obj.NewMultipartUpload("bucket", "key", nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
parts := make(map[int]string)
|
||||
|
@ -41,6 +41,8 @@ type MultipartObjectInfo struct {
|
||||
ModTime time.Time
|
||||
Size int64
|
||||
MD5Sum string
|
||||
ContentType string
|
||||
// Add more fields here.
|
||||
}
|
||||
|
||||
type byMultipartFiles []string
|
||||
@ -68,6 +70,25 @@ func (m MultipartObjectInfo) GetPartNumberOffset(offset int64) (partIndex int, p
|
||||
return
|
||||
}
|
||||
|
||||
// getMultipartObjectMeta - incomplete meta file and extract meta
|
||||
// information if any.
|
||||
func getMultipartObjectMeta(storage StorageAPI, metaFile string) (meta map[string]string, err error) {
|
||||
meta = make(map[string]string)
|
||||
offset := int64(0)
|
||||
objMetaReader, err := storage.ReadFile(minioMetaBucket, metaFile, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decoder := json.NewDecoder(objMetaReader)
|
||||
err = decoder.Decode(&meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Close the metadata reader.
|
||||
objMetaReader.Close()
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
func partNumToPartFileName(partNum int) string {
|
||||
return fmt.Sprintf("%.5d%s", partNum, multipartSuffix)
|
||||
}
|
||||
@ -78,8 +99,8 @@ func (xl xlObjects) ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMark
|
||||
}
|
||||
|
||||
// NewMultipartUpload - initialize a new multipart upload, returns a unique id.
|
||||
func (xl xlObjects) NewMultipartUpload(bucket, object string) (string, error) {
|
||||
return newMultipartUploadCommon(xl.storage, bucket, object)
|
||||
func (xl xlObjects) NewMultipartUpload(bucket, object string, meta map[string]string) (string, error) {
|
||||
return newMultipartUploadCommon(xl.storage, bucket, object, meta)
|
||||
}
|
||||
|
||||
// PutObjectPart - writes the multipart upload chunks.
|
||||
@ -148,6 +169,12 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
var metadata = MultipartObjectInfo{}
|
||||
var errs = make([]error, len(parts))
|
||||
|
||||
uploadIDIncompletePath := path.Join(mpartMetaPrefix, bucket, object, uploadID, incompleteFile)
|
||||
objMeta, err := getMultipartObjectMeta(xl.storage, uploadIDIncompletePath)
|
||||
if err != nil {
|
||||
return "", toObjectErr(err, minioMetaBucket, uploadIDIncompletePath)
|
||||
}
|
||||
|
||||
// Waitgroup to wait for go-routines.
|
||||
var wg = &sync.WaitGroup{}
|
||||
|
||||
@ -184,6 +211,8 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
|
||||
// Save successfully calculated md5sum.
|
||||
metadata.MD5Sum = s3MD5
|
||||
metadata.ContentType = objMeta["content-type"]
|
||||
|
||||
// Save modTime as well as the current time.
|
||||
metadata.ModTime = time.Now().UTC()
|
||||
|
||||
@ -244,7 +273,6 @@ func (xl xlObjects) CompleteMultipartUpload(bucket string, object string, upload
|
||||
}
|
||||
|
||||
// Delete the incomplete file place holder.
|
||||
uploadIDIncompletePath := path.Join(mpartMetaPrefix, bucket, object, uploadID, incompleteFile)
|
||||
err = xl.storage.DeleteFile(minioMetaBucket, uploadIDIncompletePath)
|
||||
if err != nil {
|
||||
return "", toObjectErr(err, minioMetaBucket, uploadIDIncompletePath)
|
||||
|
@ -17,7 +17,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@ -377,26 +376,22 @@ func (xl xlObjects) PutObject(bucket string, object string, size int64, data io.
|
||||
return "", toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
metadataBytes, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return newMD5Hex, nil
|
||||
}
|
||||
|
||||
tempMetaJSONFile := path.Join(tmpMetaPrefix, bucket, object, "meta.json")
|
||||
fileWriter, err = xl.storage.CreateFile(minioMetaBucket, tempMetaJSONFile)
|
||||
metaWriter, err := xl.storage.CreateFile(minioMetaBucket, tempMetaJSONFile)
|
||||
if err != nil {
|
||||
return "", toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
if _, err = io.Copy(fileWriter, bytes.NewReader(metadataBytes)); err != nil {
|
||||
if clErr := safeCloseAndRemove(fileWriter); clErr != nil {
|
||||
encoder := json.NewEncoder(metaWriter)
|
||||
err = encoder.Encode(&metadata)
|
||||
if err != nil {
|
||||
if clErr := safeCloseAndRemove(metaWriter); clErr != nil {
|
||||
return "", toObjectErr(clErr, bucket, object)
|
||||
}
|
||||
return "", toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
if err = fileWriter.Close(); err != nil {
|
||||
if err = safeCloseAndRemove(fileWriter); err != nil {
|
||||
if err = metaWriter.Close(); err != nil {
|
||||
if err = safeCloseAndRemove(metaWriter); err != nil {
|
||||
return "", toObjectErr(err, bucket, object)
|
||||
}
|
||||
return "", toObjectErr(err, bucket, object)
|
||||
|
Loading…
Reference in New Issue
Block a user