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
```
master
Bala FA 7 years ago committed by kannappanr
parent 2182c1a4f7
commit 3ebe61abdf
  1. 2
      cmd/api-errors.go
  2. 8
      cmd/bucket-handlers.go
  3. 3
      cmd/common-main.go
  4. 9
      cmd/fs-v1-multipart.go
  5. 6
      cmd/fs-v1.go
  6. 2
      cmd/globals.go
  7. 9
      cmd/object-api-errors.go
  8. 64
      cmd/object-handlers.go
  9. 3
      cmd/server-main.go
  10. 9
      cmd/xl-v1-multipart.go
  11. 9
      cmd/xl-v1-object.go

@ -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…
Cancel
Save