forked from TrueCloudLab/frostfs-s3-gw
[#589] Add LimitExceeded error
The Access Denied status may be received from APE due to exceeding the quota. In this situation, you need to return the appropriate error. The Conflict status is used because this error was made based on the LimitExceeded error from aws iam error https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateUser.html#API_CreateUser_Errors. Signed-off-by: Roman Loginov <r.loginov@yadro.com>
This commit is contained in:
parent
d150f8ddcb
commit
8b3252cbd0
5 changed files with 83 additions and 17 deletions
|
@ -4,6 +4,9 @@ This document outlines major changes between releases.
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Add LimitExceeded error (#589)
|
||||
|
||||
## [0.32.0] - Khumbu - 2024-12-20
|
||||
|
||||
### Added
|
||||
|
|
|
@ -290,6 +290,9 @@ const (
|
|||
//CORS configuration errors.
|
||||
ErrCORSUnsupportedMethod
|
||||
ErrCORSWildcardExposeHeaders
|
||||
|
||||
// Limits errors.
|
||||
ErrLimitExceeded
|
||||
)
|
||||
|
||||
// error code to Error structure, these fields carry respective
|
||||
|
@ -1770,6 +1773,14 @@ var errorCodes = errorCodeMap{
|
|||
Description: "Content-Range header is mandatory for this type of request",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
// The Conflict status is used because this error was made based on the LimitExceeded error
|
||||
// from aws iam error https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateUser.html#API_CreateUser_Errors.
|
||||
ErrLimitExceeded: {
|
||||
ErrCode: ErrLimitExceeded,
|
||||
Code: "LimitExceeded",
|
||||
Description: "You have reached the quota limit.",
|
||||
HTTPStatusCode: http.StatusConflict,
|
||||
},
|
||||
// Add your error structure here.
|
||||
}
|
||||
|
||||
|
@ -1833,6 +1844,10 @@ func TransformToS3Error(err error) error {
|
|||
return GetAPIError(ErrBucketAlreadyExists)
|
||||
}
|
||||
|
||||
if errors.Is(err, frostfs.ErrQuotaLimitReached) {
|
||||
return GetAPIError(ErrLimitExceeded)
|
||||
}
|
||||
|
||||
return GetAPIError(ErrInternalError)
|
||||
}
|
||||
|
||||
|
|
|
@ -240,6 +240,9 @@ var (
|
|||
|
||||
// ErrGlobalDomainIsAlreadyTaken is returned from FrostFS in case of global domain is already taken.
|
||||
ErrGlobalDomainIsAlreadyTaken = errors.New("global domain is already taken")
|
||||
|
||||
// ErrQuotaLimitReached is returned from FrostFS in case of quota exceeded.
|
||||
ErrQuotaLimitReached = errors.New("quota limit reached")
|
||||
)
|
||||
|
||||
// FrostFS represents virtual connection to FrostFS network.
|
||||
|
|
|
@ -486,6 +486,9 @@ func handleObjectError(msg string, err error) error {
|
|||
}
|
||||
|
||||
if reason, ok := frosterr.IsErrObjectAccessDenied(err); ok {
|
||||
if strings.Contains(reason, "limit reached") {
|
||||
return fmt.Errorf("%s: %w: %s", msg, frostfs.ErrQuotaLimitReached, reason)
|
||||
}
|
||||
return fmt.Errorf("%s: %w: %s", msg, frostfs.ErrAccessDenied, reason)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,31 +8,49 @@ import (
|
|||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
||||
frosterr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestErrorChecking(t *testing.T) {
|
||||
func TestHandleObjectError(t *testing.T) {
|
||||
msg := "some msg"
|
||||
|
||||
t.Run("nil error", func(t *testing.T) {
|
||||
err := handleObjectError(msg, nil)
|
||||
require.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("simple access denied", func(t *testing.T) {
|
||||
reason := "some reason"
|
||||
err := new(apistatus.ObjectAccessDenied)
|
||||
err.WriteReason(reason)
|
||||
inputErr := new(apistatus.ObjectAccessDenied)
|
||||
inputErr.WriteReason(reason)
|
||||
|
||||
var wrappedError error
|
||||
err := handleObjectError(msg, inputErr)
|
||||
require.ErrorIs(t, err, frostfs.ErrAccessDenied)
|
||||
require.Contains(t, err.Error(), reason)
|
||||
require.Contains(t, err.Error(), msg)
|
||||
})
|
||||
|
||||
if fetchedReason, ok := frosterr.IsErrObjectAccessDenied(err); ok {
|
||||
wrappedError = fmt.Errorf("%w: %s", frostfs.ErrAccessDenied, fetchedReason)
|
||||
}
|
||||
t.Run("access denied - quota reached", func(t *testing.T) {
|
||||
reason := "Quota limit reached"
|
||||
inputErr := new(apistatus.ObjectAccessDenied)
|
||||
inputErr.WriteReason(reason)
|
||||
|
||||
require.ErrorIs(t, wrappedError, frostfs.ErrAccessDenied)
|
||||
require.Contains(t, wrappedError.Error(), reason)
|
||||
}
|
||||
err := handleObjectError(msg, inputErr)
|
||||
require.ErrorIs(t, err, frostfs.ErrQuotaLimitReached)
|
||||
require.Contains(t, err.Error(), reason)
|
||||
require.Contains(t, err.Error(), msg)
|
||||
})
|
||||
|
||||
func TestErrorTimeoutChecking(t *testing.T) {
|
||||
t.Run("simple timeout", func(t *testing.T) {
|
||||
require.True(t, frosterr.IsTimeoutError(errors.New("timeout")))
|
||||
inputErr := errors.New("timeout")
|
||||
|
||||
err := handleObjectError(msg, inputErr)
|
||||
require.ErrorIs(t, err, frostfs.ErrGatewayTimeout)
|
||||
require.Contains(t, err.Error(), inputErr.Error())
|
||||
require.Contains(t, err.Error(), msg)
|
||||
})
|
||||
|
||||
t.Run("deadline exceeded", func(t *testing.T) {
|
||||
|
@ -40,11 +58,35 @@ func TestErrorTimeoutChecking(t *testing.T) {
|
|||
defer cancel()
|
||||
<-ctx.Done()
|
||||
|
||||
require.True(t, frosterr.IsTimeoutError(ctx.Err()))
|
||||
err := handleObjectError(msg, ctx.Err())
|
||||
require.ErrorIs(t, err, frostfs.ErrGatewayTimeout)
|
||||
require.Contains(t, err.Error(), ctx.Err().Error())
|
||||
require.Contains(t, err.Error(), msg)
|
||||
})
|
||||
|
||||
t.Run("grpc deadline exceeded", func(t *testing.T) {
|
||||
err := fmt.Errorf("wrap grpc error: %w", status.Error(codes.DeadlineExceeded, "error"))
|
||||
require.True(t, frosterr.IsTimeoutError(err))
|
||||
inputErr := fmt.Errorf("wrap grpc error: %w", status.Error(codes.DeadlineExceeded, "error"))
|
||||
|
||||
err := handleObjectError(msg, inputErr)
|
||||
require.ErrorIs(t, err, frostfs.ErrGatewayTimeout)
|
||||
require.Contains(t, err.Error(), inputErr.Error())
|
||||
require.Contains(t, err.Error(), msg)
|
||||
})
|
||||
|
||||
t.Run("global domain already", func(t *testing.T) {
|
||||
inputErr := errors.New("global domain is already taken")
|
||||
|
||||
err := handleObjectError(msg, inputErr)
|
||||
require.ErrorIs(t, err, frostfs.ErrGlobalDomainIsAlreadyTaken)
|
||||
require.Contains(t, err.Error(), inputErr.Error())
|
||||
require.Contains(t, err.Error(), msg)
|
||||
})
|
||||
|
||||
t.Run("unknown error", func(t *testing.T) {
|
||||
inputErr := errors.New("unknown error")
|
||||
|
||||
err := handleObjectError(msg, inputErr)
|
||||
require.ErrorIs(t, err, inputErr)
|
||||
require.Contains(t, err.Error(), msg)
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue