feat: add s3err QuotaExceeded for posix/scoutfs

When fileystem quota exceeded, the gateway will now return the
error:
S3 error: 403 (QuotaExceeded):
Your request was denied due to quota exceeded.

This will help clients to better detect upload errors due to
quota exceeded.

Fixes #483
This commit is contained in:
Ben McClelland
2024-04-06 11:53:40 -07:00
parent 27fe12367c
commit aeea61544b
3 changed files with 37 additions and 0 deletions

View File

@@ -419,6 +419,9 @@ func (p *Posix) CompleteMultipartUpload(ctx context.Context, input *s3.CompleteM
f, err := p.openTmpFile(filepath.Join(bucket, metaTmpDir), bucket, object,
totalsize, acct)
if err != nil {
if errors.Is(err, syscall.EDQUOT) {
return nil, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
return nil, fmt.Errorf("open temp file: %w", err)
}
defer f.cleanup()
@@ -431,6 +434,9 @@ func (p *Posix) CompleteMultipartUpload(ctx context.Context, input *s3.CompleteM
_, err = io.Copy(f, pf)
pf.Close()
if err != nil {
if errors.Is(err, syscall.EDQUOT) {
return nil, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
return nil, fmt.Errorf("copy part %v: %v", p.PartNumber, err)
}
}
@@ -910,6 +916,9 @@ func (p *Posix) UploadPart(ctx context.Context, input *s3.UploadPartInput) (stri
f, err := p.openTmpFile(filepath.Join(bucket, objdir),
bucket, partPath, length, acct)
if err != nil {
if errors.Is(err, syscall.EDQUOT) {
return "", s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
return "", fmt.Errorf("open temp file: %w", err)
}
@@ -917,6 +926,9 @@ func (p *Posix) UploadPart(ctx context.Context, input *s3.UploadPartInput) (stri
tr := io.TeeReader(r, hash)
_, err = io.Copy(f, tr)
if err != nil {
if errors.Is(err, syscall.EDQUOT) {
return "", s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
return "", fmt.Errorf("write part data: %w", err)
}
@@ -1009,6 +1021,9 @@ func (p *Posix) UploadPartCopy(ctx context.Context, upi *s3.UploadPartCopyInput)
f, err := p.openTmpFile(filepath.Join(*upi.Bucket, objdir),
*upi.Bucket, partPath, length, acct)
if err != nil {
if errors.Is(err, syscall.EDQUOT) {
return s3response.CopyObjectResult{}, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
return s3response.CopyObjectResult{}, fmt.Errorf("open temp file: %w", err)
}
defer f.cleanup()
@@ -1028,6 +1043,9 @@ func (p *Posix) UploadPartCopy(ctx context.Context, upi *s3.UploadPartCopyInput)
_, err = io.Copy(f, tr)
if err != nil {
if errors.Is(err, syscall.EDQUOT) {
return s3response.CopyObjectResult{}, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
return s3response.CopyObjectResult{}, fmt.Errorf("copy part data: %w", err)
}
@@ -1107,6 +1125,9 @@ func (p *Posix) PutObject(ctx context.Context, po *s3.PutObjectInput) (string, e
err = backend.MkdirAll(name, uid, gid, doChown)
if err != nil {
if errors.Is(err, syscall.EDQUOT) {
return "", s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
return "", err
}
@@ -1129,6 +1150,9 @@ func (p *Posix) PutObject(ctx context.Context, po *s3.PutObjectInput) (string, e
f, err := p.openTmpFile(filepath.Join(*po.Bucket, metaTmpDir),
*po.Bucket, *po.Key, contentLength, acct)
if err != nil {
if errors.Is(err, syscall.EDQUOT) {
return "", s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
return "", fmt.Errorf("open temp file: %w", err)
}
defer f.cleanup()
@@ -1137,6 +1161,9 @@ func (p *Posix) PutObject(ctx context.Context, po *s3.PutObjectInput) (string, e
rdr := io.TeeReader(po.Body, hash)
_, err = io.Copy(f, rdr)
if err != nil {
if errors.Is(err, syscall.EDQUOT) {
return "", s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
return "", fmt.Errorf("write object data: %w", err)
}
dir := filepath.Dir(name)

View File

@@ -25,6 +25,7 @@ import (
"os"
"path/filepath"
"strings"
"syscall"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
@@ -222,6 +223,9 @@ func (s *ScoutFS) CompleteMultipartUpload(ctx context.Context, input *s3.Complet
// extents around. so we dont want to fallocate this.
f, err := s.openTmpFile(filepath.Join(bucket, metaTmpDir), bucket, object, 0, acct)
if err != nil {
if errors.Is(err, syscall.EDQUOT) {
return nil, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
return nil, fmt.Errorf("open temp file: %w", err)
}
defer f.cleanup()

View File

@@ -116,6 +116,7 @@ const (
ErrExistingObjectIsDirectory
ErrObjectParentIsFile
ErrDirectoryObjectContainsData
ErrQuotaExceeded
)
var errorCodeResponse = map[ErrorCode]APIError{
@@ -414,6 +415,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
Description: "Directory object contains data payload.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrQuotaExceeded: {
Code: "QuotaExceeded",
Description: "Your request was denied due to quota exceeded.",
HTTPStatusCode: http.StatusForbidden,
},
}
// GetAPIError provides API Error for input API error code.