[#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. Signed-off-by: Roman Loginov <r.loginov@yadro.com>
This commit is contained in:
parent
e0ce59fd32
commit
c58334a378
4 changed files with 78 additions and 17 deletions
|
@ -289,6 +289,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
|
||||||
|
@ -1763,6 +1766,12 @@ 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,
|
||||||
},
|
},
|
||||||
|
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.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1826,6 +1835,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.
|
||||||
|
|
|
@ -477,6 +477,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, "Quota 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) {
|
||||||
|
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"
|
reason := "some reason"
|
||||||
err := new(apistatus.ObjectAccessDenied)
|
inputErr := new(apistatus.ObjectAccessDenied)
|
||||||
err.WriteReason(reason)
|
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 {
|
t.Run("access denied - quota reached", func(t *testing.T) {
|
||||||
wrappedError = fmt.Errorf("%w: %s", frostfs.ErrAccessDenied, fetchedReason)
|
reason := "Quota limit reached"
|
||||||
}
|
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.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(), err.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(), err.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