[#589] Add LimitExceeded error #593
5 changed files with 83 additions and 17 deletions
|
@ -4,6 +4,9 @@ This document outlines major changes between releases.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Add LimitExceeded error (#589)
|
||||||
|
|
||||||
## [0.32.0] - Khumbu - 2024-12-20
|
## [0.32.0] - Khumbu - 2024-12-20
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -290,6 +290,9 @@ const (
|
||||||
//CORS configuration errors.
|
//CORS configuration errors.
|
||||||
ErrCORSUnsupportedMethod
|
ErrCORSUnsupportedMethod
|
||||||
ErrCORSWildcardExposeHeaders
|
ErrCORSWildcardExposeHeaders
|
||||||
|
|
||||||
|
// Limits errors.
|
||||||
|
ErrLimitExceeded
|
||||||
)
|
)
|
||||||
|
|
||||||
// error code to Error structure, these fields carry respective
|
// 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",
|
Description: "Content-Range header is mandatory for this type of request",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
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.
|
// Add your error structure here.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1833,6 +1844,10 @@ func TransformToS3Error(err error) error {
|
||||||
return GetAPIError(ErrBucketAlreadyExists)
|
return GetAPIError(ErrBucketAlreadyExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, frostfs.ErrQuotaLimitReached) {
|
||||||
|
return GetAPIError(ErrLimitExceeded)
|
||||||
|
}
|
||||||
|
|
||||||
return GetAPIError(ErrInternalError)
|
return GetAPIError(ErrInternalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -240,6 +240,9 @@ var (
|
||||||
|
|
||||||
// ErrGlobalDomainIsAlreadyTaken is returned from FrostFS in case of global domain is already taken.
|
// ErrGlobalDomainIsAlreadyTaken is returned from FrostFS in case of global domain is already taken.
|
||||||
ErrGlobalDomainIsAlreadyTaken = errors.New("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.
|
// 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 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)
|
return fmt.Errorf("%s: %w: %s", msg, frostfs.ErrAccessDenied, reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,31 +8,49 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
"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"
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestErrorChecking(t *testing.T) {
|
func TestHandleObjectError(t *testing.T) {
|
||||||
reason := "some reason"
|
msg := "some msg"
|
||||||
err := new(apistatus.ObjectAccessDenied)
|
|
||||||
err.WriteReason(reason)
|
|
||||||
|
|
||||||
var wrappedError error
|
t.Run("nil error", func(t *testing.T) {
|
||||||
|
err := handleObjectError(msg, nil)
|
||||||
|
require.Nil(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
if fetchedReason, ok := frosterr.IsErrObjectAccessDenied(err); ok {
|
t.Run("simple access denied", func(t *testing.T) {
|
||||||
wrappedError = fmt.Errorf("%w: %s", frostfs.ErrAccessDenied, fetchedReason)
|
reason := "some reason"
|
||||||
}
|
inputErr := new(apistatus.ObjectAccessDenied)
|
||||||
|
inputErr.WriteReason(reason)
|
||||||
|
|
||||||
require.ErrorIs(t, wrappedError, frostfs.ErrAccessDenied)
|
err := handleObjectError(msg, inputErr)
|
||||||
require.Contains(t, wrappedError.Error(), reason)
|
require.ErrorIs(t, err, frostfs.ErrAccessDenied)
|
||||||
}
|
require.Contains(t, err.Error(), reason)
|
||||||
|
require.Contains(t, err.Error(), msg)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("access denied - quota reached", func(t *testing.T) {
|
||||||
|
reason := "Quota limit reached"
|
||||||
|
inputErr := new(apistatus.ObjectAccessDenied)
|
||||||
|
inputErr.WriteReason(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) {
|
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) {
|
t.Run("deadline exceeded", func(t *testing.T) {
|
||||||
|
@ -40,11 +58,35 @@ func TestErrorTimeoutChecking(t *testing.T) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
<-ctx.Done()
|
<-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) {
|
t.Run("grpc deadline exceeded", func(t *testing.T) {
|
||||||
err := fmt.Errorf("wrap grpc error: %w", status.Error(codes.DeadlineExceeded, "error"))
|
inputErr := fmt.Errorf("wrap grpc error: %w", status.Error(codes.DeadlineExceeded, "error"))
|
||||||
require.True(t, frosterr.IsTimeoutError(err))
|
|
||||||
|
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