mirror of https://github.com/minio/minio.git
Quick support to server level WORM (#5602)
This is a trival fix to support server level WORM. The feature comes with an environment variable `MINIO_WORM`. Usage: ``` $ export MINIO_WORM=on $ minio server endpoint ```
This commit is contained in:
parent
2182c1a4f7
commit
3ebe61abdf
|
@ -911,6 +911,8 @@ func toAPIErrorCode(err error) (apiErr APIErrorCode) {
|
||||||
apiErr = ErrBucketAlreadyOwnedByYou
|
apiErr = ErrBucketAlreadyOwnedByYou
|
||||||
case ObjectNotFound:
|
case ObjectNotFound:
|
||||||
apiErr = ErrNoSuchKey
|
apiErr = ErrNoSuchKey
|
||||||
|
case ObjectAlreadyExists:
|
||||||
|
apiErr = ErrMethodNotAllowed
|
||||||
case ObjectNameInvalid:
|
case ObjectNameInvalid:
|
||||||
apiErr = ErrInvalidObjectName
|
apiErr = ErrInvalidObjectName
|
||||||
case InvalidUploadID:
|
case InvalidUploadID:
|
||||||
|
|
|
@ -300,6 +300,14 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
// Not required to check whether given objects exist or not, because
|
||||||
|
// DeleteMultipleObject is always successful irrespective of object existence.
|
||||||
|
writeErrorResponse(w, ErrMethodNotAllowed, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var wg = &sync.WaitGroup{} // Allocate a new wait group.
|
var wg = &sync.WaitGroup{} // Allocate a new wait group.
|
||||||
var dErrs = make([]error, len(deleteObjects.Objects))
|
var dErrs = make([]error, len(deleteObjects.Objects))
|
||||||
|
|
||||||
|
|
|
@ -158,4 +158,7 @@ func handleCommonEnvVars() {
|
||||||
globalIsStorageClass = true
|
globalIsStorageClass = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get WORM environment variable.
|
||||||
|
globalWORMEnabled = strings.EqualFold(os.Getenv("MINIO_WORM"), "on")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Minio Cloud Storage, (C) 2016, 2017 Minio, Inc.
|
* Minio Cloud Storage, (C) 2016, 2017, 2018 Minio, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -626,6 +626,13 @@ func (fs *FSObjects) CompleteMultipartUpload(ctx context.Context, bucket string,
|
||||||
return oi, toObjectErr(errors.Trace(err), bucket, object)
|
return oi, toObjectErr(errors.Trace(err), bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if _, err = fsStatFile(pathJoin(fs.fsPath, bucket, object)); err == nil {
|
||||||
|
return ObjectInfo{}, errors.Trace(ObjectAlreadyExists{Bucket: bucket, Object: object})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = fsRenameFile(appendFilePath, pathJoin(fs.fsPath, bucket, object))
|
err = fsRenameFile(appendFilePath, pathJoin(fs.fsPath, bucket, object))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return oi, toObjectErr(errors.Trace(err), bucket, object)
|
return oi, toObjectErr(errors.Trace(err), bucket, object)
|
||||||
|
|
|
@ -745,6 +745,12 @@ func (fs *FSObjects) putObject(bucket string, object string, data *hash.Reader,
|
||||||
|
|
||||||
// Entire object was written to the temp location, now it's safe to rename it to the actual location.
|
// Entire object was written to the temp location, now it's safe to rename it to the actual location.
|
||||||
fsNSObjPath := pathJoin(fs.fsPath, bucket, object)
|
fsNSObjPath := pathJoin(fs.fsPath, bucket, object)
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if _, err = fsStatFile(fsNSObjPath); err == nil {
|
||||||
|
return ObjectInfo{}, errors.Trace(ObjectAlreadyExists{Bucket: bucket, Object: object})
|
||||||
|
}
|
||||||
|
}
|
||||||
if err = fsRenameFile(fsTmpObjPath, fsNSObjPath); err != nil {
|
if err = fsRenameFile(fsTmpObjPath, fsNSObjPath); err != nil {
|
||||||
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,6 +172,8 @@ var (
|
||||||
// Set to store standard storage class
|
// Set to store standard storage class
|
||||||
globalStandardStorageClass storageClass
|
globalStandardStorageClass storageClass
|
||||||
|
|
||||||
|
globalWORMEnabled bool
|
||||||
|
|
||||||
// Add new variable global values here.
|
// Add new variable global values here.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Minio Cloud Storage, (C) 2015 Minio, Inc.
|
* Minio Cloud Storage, (C) 2015, 2016, 2017, 2018 Minio, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -172,6 +172,13 @@ func (e ObjectNotFound) Error() string {
|
||||||
return "Object not found: " + e.Bucket + "#" + e.Object
|
return "Object not found: " + e.Bucket + "#" + e.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ObjectAlreadyExists object already exists.
|
||||||
|
type ObjectAlreadyExists GenericError
|
||||||
|
|
||||||
|
func (e ObjectAlreadyExists) Error() string {
|
||||||
|
return "Object: " + e.Bucket + "#" + e.Object + " already exists"
|
||||||
|
}
|
||||||
|
|
||||||
// ObjectExistsAsDirectory object already exists as a directory.
|
// ObjectExistsAsDirectory object already exists as a directory.
|
||||||
type ObjectExistsAsDirectory GenericError
|
type ObjectExistsAsDirectory GenericError
|
||||||
|
|
||||||
|
|
|
@ -360,6 +360,14 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if _, err = objectAPI.GetObjectInfo(ctx, dstBucket, dstObject); err == nil {
|
||||||
|
writeErrorResponse(w, ErrMethodNotAllowed, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if objectAPI.IsEncryptionSupported() {
|
if objectAPI.IsEncryptionSupported() {
|
||||||
if apiErr, _ := DecryptCopyObjectInfo(&srcInfo, r.Header); apiErr != ErrNone {
|
if apiErr, _ := DecryptCopyObjectInfo(&srcInfo, r.Header); apiErr != ErrNone {
|
||||||
writeErrorResponse(w, apiErr, r.URL)
|
writeErrorResponse(w, apiErr, r.URL)
|
||||||
|
@ -681,6 +689,14 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if _, err = objectAPI.GetObjectInfo(ctx, bucket, object); err == nil {
|
||||||
|
writeErrorResponse(w, ErrMethodNotAllowed, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if objectAPI.IsEncryptionSupported() {
|
if objectAPI.IsEncryptionSupported() {
|
||||||
if hasSSECustomerHeader(r.Header) && !hasSuffix(object, slashSeparator) { // handle SSE-C requests
|
if hasSSECustomerHeader(r.Header) && !hasSuffix(object, slashSeparator) { // handle SSE-C requests
|
||||||
reader, err = EncryptRequest(hashReader, r, metadata)
|
reader, err = EncryptRequest(hashReader, r, metadata)
|
||||||
|
@ -753,6 +769,14 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if _, err := objectAPI.GetObjectInfo(ctx, bucket, object); err == nil {
|
||||||
|
writeErrorResponse(w, ErrMethodNotAllowed, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate storage class metadata if present
|
// Validate storage class metadata if present
|
||||||
if _, ok := r.Header[amzStorageClassCanonical]; ok {
|
if _, ok := r.Header[amzStorageClassCanonical]; ok {
|
||||||
if !isValidStorageClassMeta(r.Header.Get(amzStorageClassCanonical)) {
|
if !isValidStorageClassMeta(r.Header.Get(amzStorageClassCanonical)) {
|
||||||
|
@ -863,6 +887,14 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if _, err = objectAPI.GetObjectInfo(ctx, dstBucket, dstObject); err == nil {
|
||||||
|
writeErrorResponse(w, ErrMethodNotAllowed, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if objectAPI.IsEncryptionSupported() {
|
if objectAPI.IsEncryptionSupported() {
|
||||||
if apiErr, _ := DecryptCopyObjectInfo(&srcInfo, r.Header); apiErr != ErrNone {
|
if apiErr, _ := DecryptCopyObjectInfo(&srcInfo, r.Header); apiErr != ErrNone {
|
||||||
writeErrorResponse(w, apiErr, r.URL)
|
writeErrorResponse(w, apiErr, r.URL)
|
||||||
|
@ -1119,6 +1151,14 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if _, err = objectAPI.GetObjectInfo(ctx, bucket, object); err == nil {
|
||||||
|
writeErrorResponse(w, ErrMethodNotAllowed, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if objectAPI.IsEncryptionSupported() {
|
if objectAPI.IsEncryptionSupported() {
|
||||||
var li ListPartsInfo
|
var li ListPartsInfo
|
||||||
li, err = objectAPI.ListObjectParts(ctx, bucket, object, uploadID, 0, 1)
|
li, err = objectAPI.ListObjectParts(ctx, bucket, object, uploadID, 0, 1)
|
||||||
|
@ -1200,6 +1240,14 @@ func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if _, err := objectAPI.GetObjectInfo(ctx, bucket, object); err == nil {
|
||||||
|
writeErrorResponse(w, ErrMethodNotAllowed, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uploadID, _, _, _ := getObjectResources(r.URL.Query())
|
uploadID, _, _, _ := getObjectResources(r.URL.Query())
|
||||||
if err := objectAPI.AbortMultipartUpload(ctx, bucket, object, uploadID); err != nil {
|
if err := objectAPI.AbortMultipartUpload(ctx, bucket, object, uploadID); err != nil {
|
||||||
errorIf(err, "AbortMultipartUpload failed")
|
errorIf(err, "AbortMultipartUpload failed")
|
||||||
|
@ -1268,6 +1316,14 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if _, err := objectAPI.GetObjectInfo(ctx, bucket, object); err == nil {
|
||||||
|
writeErrorResponse(w, ErrMethodNotAllowed, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get upload id.
|
// Get upload id.
|
||||||
uploadID, _, _, _ := getObjectResources(r.URL.Query())
|
uploadID, _, _, _ := getObjectResources(r.URL.Query())
|
||||||
|
|
||||||
|
@ -1366,6 +1422,14 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
// Not required to check whether given object exists or not, because
|
||||||
|
// DeleteObject is always successful irrespective of object existence.
|
||||||
|
writeErrorResponse(w, ErrMethodNotAllowed, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectDELETE.html
|
// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectDELETE.html
|
||||||
// Ignore delete object errors while replying to client, since we are
|
// Ignore delete object errors while replying to client, since we are
|
||||||
// suppposed to reply only 204. Additionally log the error for
|
// suppposed to reply only 204. Additionally log the error for
|
||||||
|
|
|
@ -76,6 +76,9 @@ ENVIRONMENT VARIABLES:
|
||||||
DOMAIN:
|
DOMAIN:
|
||||||
MINIO_DOMAIN: To enable virtual-host-style requests. Set this value to Minio host domain name.
|
MINIO_DOMAIN: To enable virtual-host-style requests. Set this value to Minio host domain name.
|
||||||
|
|
||||||
|
WORM:
|
||||||
|
MINIO_WORM: To turn on Write-Once-Read-Many in server, set this value to "on".
|
||||||
|
|
||||||
EXAMPLES:
|
EXAMPLES:
|
||||||
1. Start minio server on "/home/shared" directory.
|
1. Start minio server on "/home/shared" directory.
|
||||||
$ {{.HelpName}} /home/shared
|
$ {{.HelpName}} /home/shared
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Minio Cloud Storage, (C) 2016, 2017 Minio, Inc.
|
* Minio Cloud Storage, (C) 2016, 2017, 2018 Minio, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -728,6 +728,13 @@ func (xl xlObjects) CompleteMultipartUpload(ctx context.Context, bucket string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if xl.isObject(bucket, object) {
|
||||||
|
return ObjectInfo{}, errors.Trace(ObjectAlreadyExists{Bucket: bucket, Object: object})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Rename the multipart object to final location.
|
// Rename the multipart object to final location.
|
||||||
if _, err = renameObject(onlineDisks, minioMetaMultipartBucket, uploadIDPath, bucket, object, writeQuorum); err != nil {
|
if _, err = renameObject(onlineDisks, minioMetaMultipartBucket, uploadIDPath, bucket, object, writeQuorum); err != nil {
|
||||||
return oi, toObjectErr(err, bucket, object)
|
return oi, toObjectErr(err, bucket, object)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Minio Cloud Storage, (C) 2016, 2017 Minio, Inc.
|
* Minio Cloud Storage, (C) 2016, 2017, 2018 Minio, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -693,6 +693,13 @@ func (xl xlObjects) putObject(bucket string, object string, data *hash.Reader, m
|
||||||
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
if xl.isObject(bucket, object) {
|
||||||
|
return ObjectInfo{}, errors.Trace(ObjectAlreadyExists{Bucket: bucket, Object: object})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Rename the successfully written temporary object to final location.
|
// Rename the successfully written temporary object to final location.
|
||||||
if _, err = renameObject(onlineDisks, minioMetaTmpBucket, tempObj, bucket, object, writeQuorum); err != nil {
|
if _, err = renameObject(onlineDisks, minioMetaTmpBucket, tempObj, bucket, object, writeQuorum); err != nil {
|
||||||
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
||||||
|
|
Loading…
Reference in New Issue