Merge pull request #2096 from versity/ben/enospc

feat: add new ErrNoSpaceLeftOnDevice API error for ENOSPC errors
This commit is contained in:
Ben McClelland
2026-04-29 13:48:02 -07:00
committed by GitHub
4 changed files with 54 additions and 0 deletions

View File

@@ -20,6 +20,9 @@ import (
"io"
"os"
"path/filepath"
"syscall"
"github.com/versity/versitygw/s3err"
)
// SideCar is a metadata storer that uses sidecar files to store metadata.
@@ -71,12 +74,18 @@ func (s SideCar) StoreAttribute(_ *os.File, bucket, object, attribute string, va
}
err := os.MkdirAll(metadir, 0777)
if err != nil {
if errors.Is(err, syscall.ENOSPC) {
return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return fmt.Errorf("failed to create metadata directory: %v", err)
}
attr := filepath.Join(metadir, attribute)
tempfile, err := os.CreateTemp(metadir, attribute)
if err != nil {
if errors.Is(err, syscall.ENOSPC) {
return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return fmt.Errorf("failed to create temporary file: %v", err)
}
defer os.Remove(tempfile.Name())
@@ -84,6 +93,9 @@ func (s SideCar) StoreAttribute(_ *os.File, bucket, object, attribute string, va
_, err = tempfile.Write(value)
if err != nil {
tempfile.Close()
if errors.Is(err, syscall.ENOSPC) {
return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return fmt.Errorf("failed to write attribute: %v", err)
}
@@ -176,6 +188,9 @@ func (s SideCar) RenameObject(bucket, oldObject, newObject string) error {
newPath := filepath.Join(s.dir, bucket, newObject)
if err := os.MkdirAll(filepath.Dir(newPath), 0777); err != nil {
if errors.Is(err, syscall.ENOSPC) {
return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return fmt.Errorf("create parent for renamed metadata: %w", err)
}

View File

@@ -57,6 +57,9 @@ func (x XattrMeta) StoreAttribute(f *os.File, bucket, object, attribute string,
if errors.Is(err, syscall.EROFS) {
return s3err.GetAPIError(s3err.ErrMethodNotAllowed)
}
if errors.Is(err, syscall.ENOSPC) {
return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return err
}
@@ -64,6 +67,9 @@ func (x XattrMeta) StoreAttribute(f *os.File, bucket, object, attribute string,
if errors.Is(err, syscall.EROFS) {
return s3err.GetAPIError(s3err.ErrMethodNotAllowed)
}
if errors.Is(err, syscall.ENOSPC) {
return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return err
}

View File

@@ -1989,6 +1989,9 @@ func (p *Posix) CompleteMultipartUploadWithCopy(ctx context.Context, input *s3.C
if errors.Is(err, syscall.EDQUOT) {
return res, "", s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
if errors.Is(err, syscall.ENOSPC) {
return res, "", s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return res, "", fmt.Errorf("open temp file: %w", err)
}
defer f.cleanup()
@@ -2044,6 +2047,9 @@ func (p *Posix) CompleteMultipartUploadWithCopy(ctx context.Context, input *s3.C
if errors.Is(err, syscall.EDQUOT) {
return res, "", s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
if errors.Is(err, syscall.ENOSPC) {
return res, "", s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return res, "", fmt.Errorf("copy part %v: %v", part.PartNumber, err)
}
}
@@ -2877,6 +2883,9 @@ func (p *Posix) UploadPartWithPostFunc(ctx context.Context, input *s3.UploadPart
if errors.Is(err, syscall.EDQUOT) {
return nil, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
if errors.Is(err, syscall.ENOSPC) {
return nil, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return nil, fmt.Errorf("open temp file: %w", err)
}
defer f.cleanup()
@@ -2989,6 +2998,9 @@ func (p *Posix) UploadPartWithPostFunc(ctx context.Context, input *s3.UploadPart
if errors.Is(err, syscall.EDQUOT) {
return nil, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
if errors.Is(err, syscall.ENOSPC) {
return nil, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
// Return the error itself, if it's an 's3err.APIError'
if _, ok := err.(s3err.APIError); ok {
return nil, err
@@ -3253,6 +3265,9 @@ func (p *Posix) UploadPartCopy(ctx context.Context, upi *s3.UploadPartCopyInput)
if errors.Is(err, syscall.EDQUOT) {
return s3response.CopyPartResult{}, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
if errors.Is(err, syscall.ENOSPC) {
return s3response.CopyPartResult{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return s3response.CopyPartResult{}, fmt.Errorf("open temp file: %w", err)
}
defer f.cleanup()
@@ -3298,6 +3313,9 @@ func (p *Posix) UploadPartCopy(ctx context.Context, upi *s3.UploadPartCopyInput)
if errors.Is(err, syscall.EDQUOT) {
return s3response.CopyPartResult{}, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
if errors.Is(err, syscall.ENOSPC) {
return s3response.CopyPartResult{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return s3response.CopyPartResult{}, fmt.Errorf("copy part data: %w", err)
}
@@ -3494,6 +3512,9 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje
if errors.Is(err, syscall.EDQUOT) {
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
if errors.Is(err, syscall.ENOSPC) {
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return s3response.PutObjectOutput{}, err
}
@@ -3622,6 +3643,9 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje
if errors.Is(err, syscall.EDQUOT) {
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
if errors.Is(err, syscall.ENOSPC) {
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
return s3response.PutObjectOutput{}, fmt.Errorf("open temp file: %w", err)
}
defer f.cleanup()
@@ -3646,6 +3670,9 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje
if errors.Is(err, syscall.EDQUOT) {
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrQuotaExceeded)
}
if errors.Is(err, syscall.ENOSPC) {
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
}
// Return the error itself, if it's an 's3err.APIError'
if _, ok := err.(s3err.APIError); ok {
return s3response.PutObjectOutput{}, err

View File

@@ -199,6 +199,7 @@ const (
ErrQuotaExceeded
ErrVersioningNotConfigured
ErrACLsDisabled
ErrNoSpaceLeftOnDevice
// Admin api errors
ErrAdminAccessDenied
@@ -904,6 +905,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
Description: "Access control lists are disabled at the gateway level",
HTTPStatusCode: http.StatusBadRequest,
},
ErrNoSpaceLeftOnDevice: {
Code: "InsufficientStorage",
Description: "No space left on device.",
HTTPStatusCode: http.StatusInsufficientStorage,
},
// Admin api errors
ErrAdminAccessDenied: {