[#187] Add handling quota limit reached error
All checks were successful
/ DCO (pull_request) Successful in 2m39s
/ Vulncheck (pull_request) Successful in 2m55s
/ Builds (pull_request) Successful in 4m18s
/ Lint (pull_request) Successful in 4m24s
/ Tests (pull_request) Successful in 3m59s

The Access Denied status may be received
from APE due to exceeding the quota. In
this situation, you need to return the
appropriate status code.

Signed-off-by: Roman Loginov <r.loginov@yadro.com>
This commit is contained in:
Roman Loginov 2024-12-26 09:20:06 +03:00
parent a4e3767d4b
commit 7f94699ec6
14 changed files with 280 additions and 169 deletions

View file

@ -92,6 +92,7 @@ The `filename` field from the multipart form will be set as `FileName` attribute
|--------|----------------------------------------------| |--------|----------------------------------------------|
| 200 | Object created successfully. | | 200 | Object created successfully. |
| 400 | Some error occurred during object uploading. | | 400 | Some error occurred during object uploading. |
| 409 | The quota was exceeded. |
## Get object ## Get object

View file

@ -11,6 +11,7 @@ import (
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
@ -205,8 +206,8 @@ func (h *Handler) getDirObjectsNative(ctx context.Context, bucketInfo *data.Buck
filters.AddFilter(object.AttributeFilePath, prefix, object.MatchCommonPrefix) filters.AddFilter(object.AttributeFilePath, prefix, object.MatchCommonPrefix)
} }
prm := PrmObjectSearch{ prm := layer.PrmObjectSearch{
PrmAuth: PrmAuth{ PrmAuth: layer.PrmAuth{
BearerToken: bearerToken(ctx), BearerToken: bearerToken(ctx),
}, },
Container: bucketInfo.CID, Container: bucketInfo.CID,
@ -253,7 +254,7 @@ type ResponseObjectExtended struct {
Error error Error error
} }
func (h *Handler) headDirObjects(ctx context.Context, cnrID cid.ID, objectIDs ResObjectSearch, basePath string) (<-chan ResponseObjectExtended, error) { func (h *Handler) headDirObjects(ctx context.Context, cnrID cid.ID, objectIDs layer.ResObjectSearch, basePath string) (<-chan ResponseObjectExtended, error) {
res := make(chan ResponseObjectExtended) res := make(chan ResponseObjectExtended)
go func() { go func() {
@ -293,8 +294,8 @@ func (h *Handler) headDirObjects(ctx context.Context, cnrID cid.ID, objectIDs Re
func (h *Handler) headDirObject(ctx context.Context, cnrID cid.ID, objID oid.ID, basePath string) (ResponseObject, error) { func (h *Handler) headDirObject(ctx context.Context, cnrID cid.ID, objID oid.ID, basePath string) (ResponseObject, error) {
addr := newAddress(cnrID, objID) addr := newAddress(cnrID, objID)
obj, err := h.frostfs.HeadObject(ctx, PrmObjectHead{ obj, err := h.frostfs.HeadObject(ctx, layer.PrmObjectHead{
PrmAuth: PrmAuth{BearerToken: bearerToken(ctx)}, PrmAuth: layer.PrmAuth{BearerToken: bearerToken(ctx)},
Address: addr, Address: addr,
}) })
if err != nil { if err != nil {

View file

@ -75,13 +75,13 @@ func (h *Handler) DownloadByAttribute(c *fasthttp.RequestCtx) {
h.byAttribute(c, h.receiveFile) h.byAttribute(c, h.receiveFile)
} }
func (h *Handler) search(ctx context.Context, cnrID cid.ID, key, val string, op object.SearchMatchType) (ResObjectSearch, error) { func (h *Handler) search(ctx context.Context, cnrID cid.ID, key, val string, op object.SearchMatchType) (layer.ResObjectSearch, error) {
filters := object.NewSearchFilters() filters := object.NewSearchFilters()
filters.AddRootFilter() filters.AddRootFilter()
filters.AddFilter(key, val, op) filters.AddFilter(key, val, op)
prm := PrmObjectSearch{ prm := layer.PrmObjectSearch{
PrmAuth: PrmAuth{ PrmAuth: layer.PrmAuth{
BearerToken: bearerToken(ctx), BearerToken: bearerToken(ctx),
}, },
Container: cnrID, Container: cnrID,
@ -184,8 +184,8 @@ func (h *Handler) DownloadZipped(c *fasthttp.RequestCtx) {
} }
func (h *Handler) zipObject(ctx context.Context, zipWriter *zip.Writer, addr oid.Address, btoken *bearer.Token, bufZip []byte) error { func (h *Handler) zipObject(ctx context.Context, zipWriter *zip.Writer, addr oid.Address, btoken *bearer.Token, bufZip []byte) error {
prm := PrmObjectGet{ prm := layer.PrmObjectGet{
PrmAuth: PrmAuth{ PrmAuth: layer.PrmAuth{
BearerToken: btoken, BearerToken: btoken,
}, },
Address: addr, Address: addr,

View file

@ -9,6 +9,7 @@ import (
"io" "io"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
@ -58,7 +59,7 @@ func (t *TestFrostFS) AllowUserOperation(cnrID cid.ID, userID user.ID, op acl.Op
t.accessList[fmt.Sprintf("%s/%s/%s/%s", cnrID, userID, op, objID)] = true t.accessList[fmt.Sprintf("%s/%s/%s/%s", cnrID, userID, op, objID)] = true
} }
func (t *TestFrostFS) Container(_ context.Context, prm PrmContainer) (*container.Container, error) { func (t *TestFrostFS) Container(_ context.Context, prm layer.PrmContainer) (*container.Container, error) {
for k, v := range t.containers { for k, v := range t.containers {
if k == prm.ContainerID.EncodeToString() { if k == prm.ContainerID.EncodeToString() {
return v, nil return v, nil
@ -85,7 +86,7 @@ func (t *TestFrostFS) retrieveObject(addr oid.Address, btoken *bearer.Token) (*o
owner := t.requestOwner(btoken) owner := t.requestOwner(btoken)
if !t.isAllowed(addr.Container(), owner, acl.OpObjectGet, addr.Object()) { if !t.isAllowed(addr.Container(), owner, acl.OpObjectGet, addr.Object()) {
return nil, ErrAccessDenied return nil, layer.ErrAccessDenied
} }
return obj, nil return obj, nil
@ -94,23 +95,23 @@ func (t *TestFrostFS) retrieveObject(addr oid.Address, btoken *bearer.Token) (*o
return nil, fmt.Errorf("%w: %s", &apistatus.ObjectNotFound{}, addr) return nil, fmt.Errorf("%w: %s", &apistatus.ObjectNotFound{}, addr)
} }
func (t *TestFrostFS) HeadObject(_ context.Context, prm PrmObjectHead) (*object.Object, error) { func (t *TestFrostFS) HeadObject(_ context.Context, prm layer.PrmObjectHead) (*object.Object, error) {
return t.retrieveObject(prm.Address, prm.BearerToken) return t.retrieveObject(prm.Address, prm.BearerToken)
} }
func (t *TestFrostFS) GetObject(_ context.Context, prm PrmObjectGet) (*Object, error) { func (t *TestFrostFS) GetObject(_ context.Context, prm layer.PrmObjectGet) (*layer.Object, error) {
obj, err := t.retrieveObject(prm.Address, prm.BearerToken) obj, err := t.retrieveObject(prm.Address, prm.BearerToken)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Object{ return &layer.Object{
Header: *obj, Header: *obj,
Payload: io.NopCloser(bytes.NewReader(obj.Payload())), Payload: io.NopCloser(bytes.NewReader(obj.Payload())),
}, nil }, nil
} }
func (t *TestFrostFS) RangeObject(_ context.Context, prm PrmObjectRange) (io.ReadCloser, error) { func (t *TestFrostFS) RangeObject(_ context.Context, prm layer.PrmObjectRange) (io.ReadCloser, error) {
obj, err := t.retrieveObject(prm.Address, prm.BearerToken) obj, err := t.retrieveObject(prm.Address, prm.BearerToken)
if err != nil { if err != nil {
return nil, err return nil, err
@ -121,7 +122,7 @@ func (t *TestFrostFS) RangeObject(_ context.Context, prm PrmObjectRange) (io.Rea
return io.NopCloser(bytes.NewReader(payload)), nil return io.NopCloser(bytes.NewReader(payload)), nil
} }
func (t *TestFrostFS) CreateObject(_ context.Context, prm PrmObjectCreate) (oid.ID, error) { func (t *TestFrostFS) CreateObject(_ context.Context, prm layer.PrmObjectCreate) (oid.ID, error) {
b := make([]byte, 32) b := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, b); err != nil { if _, err := io.ReadFull(rand.Reader, b); err != nil {
return oid.ID{}, err return oid.ID{}, err
@ -158,7 +159,7 @@ func (t *TestFrostFS) CreateObject(_ context.Context, prm PrmObjectCreate) (oid.
owner := t.requestOwner(prm.BearerToken) owner := t.requestOwner(prm.BearerToken)
if !t.isAllowed(cnrID, owner, acl.OpObjectPut, objID) { if !t.isAllowed(cnrID, owner, acl.OpObjectPut, objID) {
return oid.ID{}, ErrAccessDenied return oid.ID{}, layer.ErrAccessDenied
} }
addr := newAddress(cnrID, objID) addr := newAddress(cnrID, objID)
@ -195,9 +196,9 @@ func (r *resObjectSearchMock) Iterate(f func(oid.ID) bool) error {
func (r *resObjectSearchMock) Close() {} func (r *resObjectSearchMock) Close() {}
func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) (ResObjectSearch, error) { func (t *TestFrostFS) SearchObjects(_ context.Context, prm layer.PrmObjectSearch) (layer.ResObjectSearch, error) {
if !t.isAllowed(prm.Container, t.requestOwner(prm.BearerToken), acl.OpObjectSearch, oid.ID{}) { if !t.isAllowed(prm.Container, t.requestOwner(prm.BearerToken), acl.OpObjectSearch, oid.ID{}) {
return nil, ErrAccessDenied return nil, layer.ErrAccessDenied
} }
cidStr := prm.Container.EncodeToString() cidStr := prm.Container.EncodeToString()
@ -229,7 +230,7 @@ func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) (Res
return &resObjectSearchMock{res: res}, nil return &resObjectSearchMock{res: res}, nil
} }
func (t *TestFrostFS) InitMultiObjectReader(context.Context, PrmInitMultiObjectReader) (io.Reader, error) { func (t *TestFrostFS) InitMultiObjectReader(context.Context, layer.PrmInitMultiObjectReader) (io.Reader, error) {
return nil, nil return nil, nil
} }

View file

@ -15,7 +15,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
@ -38,130 +37,13 @@ type Config interface {
EnableFilepathFallback() bool EnableFilepathFallback() bool
} }
// PrmContainer groups parameters of FrostFS.Container operation.
type PrmContainer struct {
// Container identifier.
ContainerID cid.ID
}
// PrmAuth groups authentication parameters for the FrostFS operation.
type PrmAuth struct {
// Bearer token to be used for the operation. Overlaps PrivateKey. Optional.
BearerToken *bearer.Token
}
// PrmObjectHead groups parameters of FrostFS.HeadObject operation.
type PrmObjectHead struct {
// Authentication parameters.
PrmAuth
// Address to read the object header from.
Address oid.Address
}
// PrmObjectGet groups parameters of FrostFS.GetObject operation.
type PrmObjectGet struct {
// Authentication parameters.
PrmAuth
// Address to read the object header from.
Address oid.Address
}
// PrmObjectRange groups parameters of FrostFS.RangeObject operation.
type PrmObjectRange struct {
// Authentication parameters.
PrmAuth
// Address to read the object header from.
Address oid.Address
// Offset-length range of the object payload to be read.
PayloadRange [2]uint64
}
// Object represents FrostFS object.
type Object struct {
// Object header (doesn't contain payload).
Header object.Object
// Object payload part encapsulated in io.Reader primitive.
// Returns ErrAccessDenied on read access violation.
Payload io.ReadCloser
}
// PrmObjectCreate groups parameters of FrostFS.CreateObject operation.
type PrmObjectCreate struct {
// Authentication parameters.
PrmAuth
Object *object.Object
// Object payload encapsulated in io.Reader primitive.
Payload io.Reader
// Enables client side object preparing.
ClientCut bool
// Disables using Tillich-Zémor hash for payload.
WithoutHomomorphicHash bool
// Sets max buffer size to read payload.
BufferMaxSize uint64
}
// PrmObjectSearch groups parameters of FrostFS.sear SearchObjects operation.
type PrmObjectSearch struct {
// Authentication parameters.
PrmAuth
// Container to select the objects from.
Container cid.ID
Filters object.SearchFilters
}
type PrmInitMultiObjectReader struct {
// payload range
Off, Ln uint64
Addr oid.Address
Bearer *bearer.Token
}
type ResObjectSearch interface {
Read(buf []oid.ID) (int, error)
Iterate(f func(oid.ID) bool) error
Close()
}
var (
// ErrAccessDenied is returned from FrostFS in case of access violation.
ErrAccessDenied = errors.New("access denied")
// ErrGatewayTimeout is returned from FrostFS in case of timeout, deadline exceeded etc.
ErrGatewayTimeout = errors.New("gateway timeout")
)
// FrostFS represents virtual connection to FrostFS network.
type FrostFS interface {
Container(context.Context, PrmContainer) (*container.Container, error)
HeadObject(context.Context, PrmObjectHead) (*object.Object, error)
GetObject(context.Context, PrmObjectGet) (*Object, error)
RangeObject(context.Context, PrmObjectRange) (io.ReadCloser, error)
CreateObject(context.Context, PrmObjectCreate) (oid.ID, error)
SearchObjects(context.Context, PrmObjectSearch) (ResObjectSearch, error)
InitMultiObjectReader(ctx context.Context, p PrmInitMultiObjectReader) (io.Reader, error)
utils.EpochInfoFetcher
}
type ContainerResolver interface { type ContainerResolver interface {
Resolve(ctx context.Context, name string) (*cid.ID, error) Resolve(ctx context.Context, name string) (*cid.ID, error)
} }
type Handler struct { type Handler struct {
log *zap.Logger log *zap.Logger
frostfs FrostFS frostfs layer.FrostFS
ownerID *user.ID ownerID *user.ID
config Config config Config
containerResolver ContainerResolver containerResolver ContainerResolver
@ -172,7 +54,7 @@ type Handler struct {
type AppParams struct { type AppParams struct {
Logger *zap.Logger Logger *zap.Logger
FrostFS FrostFS FrostFS layer.FrostFS
Owner *user.ID Owner *user.ID
Resolver ContainerResolver Resolver ContainerResolver
Cache *cache.BucketCache Cache *cache.BucketCache
@ -348,7 +230,7 @@ func (h *Handler) getBucketInfo(ctx context.Context, containerName string, log *
} }
func (h *Handler) readContainer(ctx context.Context, cnrID cid.ID) (*data.BucketInfo, error) { func (h *Handler) readContainer(ctx context.Context, cnrID cid.ID) (*data.BucketInfo, error) {
prm := PrmContainer{ContainerID: cnrID} prm := layer.PrmContainer{ContainerID: cnrID}
res, err := h.frostfs.Container(ctx, prm) res, err := h.frostfs.Container(ctx, prm)
if err != nil { if err != nil {
return nil, fmt.Errorf("get frostfs container '%s': %w", cnrID.String(), err) return nil, fmt.Errorf("get frostfs container '%s': %w", cnrID.String(), err)

View file

@ -31,8 +31,8 @@ func (h *Handler) headObject(ctx context.Context, req request, objectAddress oid
btoken := bearerToken(ctx) btoken := bearerToken(ctx)
prm := PrmObjectHead{ prm := layer.PrmObjectHead{
PrmAuth: PrmAuth{ PrmAuth: layer.PrmAuth{
BearerToken: btoken, BearerToken: btoken,
}, },
Address: objectAddress, Address: objectAddress,
@ -76,8 +76,8 @@ func (h *Handler) headObject(ctx context.Context, req request, objectAddress oid
if len(contentType) == 0 { if len(contentType) == 0 {
contentType, _, err = readContentType(obj.PayloadSize(), func(sz uint64) (io.Reader, error) { contentType, _, err = readContentType(obj.PayloadSize(), func(sz uint64) (io.Reader, error) {
prmRange := PrmObjectRange{ prmRange := layer.PrmObjectRange{
PrmAuth: PrmAuth{ PrmAuth: layer.PrmAuth{
BearerToken: btoken, BearerToken: btoken,
}, },
Address: objectAddress, Address: objectAddress,

View file

@ -6,6 +6,7 @@ import (
"strconv" "strconv"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/multipart" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/multipart"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -65,7 +66,7 @@ func (h *Handler) getPayload(p getMultiobjectBodyParams) (io.ReadCloser, uint64,
return nil, 0, err return nil, 0, err
} }
ctx := p.req.RequestCtx ctx := p.req.RequestCtx
params := PrmInitMultiObjectReader{ params := layer.PrmInitMultiObjectReader{
Addr: newAddress(cid, oid), Addr: newAddress(cid, oid),
Bearer: bearerToken(ctx), Bearer: bearerToken(ctx),
} }

View file

@ -9,6 +9,7 @@ import (
"strconv" "strconv"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
@ -48,7 +49,7 @@ func readContentType(maxSize uint64, rInit func(uint64) (io.Reader, error)) (str
} }
type getMultiobjectBodyParams struct { type getMultiobjectBodyParams struct {
obj *Object obj *layer.Object
req request req request
strSize string strSize string
} }
@ -62,8 +63,8 @@ func (h *Handler) receiveFile(ctx context.Context, req request, objAddress oid.A
contentType string contentType string
) )
prm := PrmObjectGet{ prm := layer.PrmObjectGet{
PrmAuth: PrmAuth{ PrmAuth: layer.PrmAuth{
BearerToken: bearerToken(ctx), BearerToken: bearerToken(ctx),
}, },
Address: objAddress, Address: objAddress,

View file

@ -8,6 +8,7 @@ import (
"strconv" "strconv"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens"
@ -135,8 +136,8 @@ func (h *Handler) Upload(c *fasthttp.RequestCtx) {
obj.SetOwnerID(*h.ownerID) obj.SetOwnerID(*h.ownerID)
obj.SetAttributes(attributes...) obj.SetAttributes(attributes...)
prm := PrmObjectCreate{ prm := layer.PrmObjectCreate{
PrmAuth: PrmAuth{ PrmAuth: layer.PrmAuth{
BearerToken: h.fetchBearerToken(ctx), BearerToken: h.fetchBearerToken(ctx),
}, },
Object: obj, Object: obj,

133
internal/layer/frostfs.go Normal file
View file

@ -0,0 +1,133 @@
package layer
import (
"context"
"errors"
"io"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
)
// PrmContainer groups parameters of FrostFS.Container operation.
type PrmContainer struct {
// Container identifier.
ContainerID cid.ID
}
// PrmAuth groups authentication parameters for the FrostFS operation.
type PrmAuth struct {
// Bearer token to be used for the operation. Overlaps PrivateKey. Optional.
BearerToken *bearer.Token
}
// PrmObjectHead groups parameters of FrostFS.HeadObject operation.
type PrmObjectHead struct {
// Authentication parameters.
PrmAuth
// Address to read the object header from.
Address oid.Address
}
// PrmObjectGet groups parameters of FrostFS.GetObject operation.
type PrmObjectGet struct {
// Authentication parameters.
PrmAuth
// Address to read the object header from.
Address oid.Address
}
// PrmObjectRange groups parameters of FrostFS.RangeObject operation.
type PrmObjectRange struct {
// Authentication parameters.
PrmAuth
// Address to read the object header from.
Address oid.Address
// Offset-length range of the object payload to be read.
PayloadRange [2]uint64
}
// Object represents FrostFS object.
type Object struct {
// Object header (doesn't contain payload).
Header object.Object
// Object payload part encapsulated in io.Reader primitive.
// Returns ErrAccessDenied on read access violation.
Payload io.ReadCloser
}
// PrmObjectCreate groups parameters of FrostFS.CreateObject operation.
type PrmObjectCreate struct {
// Authentication parameters.
PrmAuth
Object *object.Object
// Object payload encapsulated in io.Reader primitive.
Payload io.Reader
// Enables client side object preparing.
ClientCut bool
// Disables using Tillich-Zémor hash for payload.
WithoutHomomorphicHash bool
// Sets max buffer size to read payload.
BufferMaxSize uint64
}
// PrmObjectSearch groups parameters of FrostFS.sear SearchObjects operation.
type PrmObjectSearch struct {
// Authentication parameters.
PrmAuth
// Container to select the objects from.
Container cid.ID
Filters object.SearchFilters
}
type PrmInitMultiObjectReader struct {
// payload range
Off, Ln uint64
Addr oid.Address
Bearer *bearer.Token
}
type ResObjectSearch interface {
Read(buf []oid.ID) (int, error)
Iterate(f func(oid.ID) bool) error
Close()
}
var (
// ErrAccessDenied is returned from FrostFS in case of access violation.
ErrAccessDenied = errors.New("access denied")
// ErrGatewayTimeout is returned from FrostFS in case of timeout, deadline exceeded etc.
ErrGatewayTimeout = errors.New("gateway timeout")
// ErrQuotaLimitReached is returned from FrostFS in case of quota exceeded.
ErrQuotaLimitReached = errors.New("quota limit reached")
)
// FrostFS represents virtual connection to FrostFS network.
type FrostFS interface {
Container(context.Context, PrmContainer) (*container.Container, error)
HeadObject(context.Context, PrmObjectHead) (*object.Object, error)
GetObject(context.Context, PrmObjectGet) (*Object, error)
RangeObject(context.Context, PrmObjectRange) (io.ReadCloser, error)
CreateObject(context.Context, PrmObjectCreate) (oid.ID, error)
SearchObjects(context.Context, PrmObjectSearch) (ResObjectSearch, error)
InitMultiObjectReader(ctx context.Context, p PrmInitMultiObjectReader) (io.Reader, error)
utils.EpochInfoFetcher
}

View file

@ -7,7 +7,7 @@ import (
"io" "io"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
@ -33,7 +33,7 @@ func NewFrostFS(p *pool.Pool) *FrostFS {
} }
// Container implements frostfs.FrostFS interface method. // Container implements frostfs.FrostFS interface method.
func (x *FrostFS) Container(ctx context.Context, containerPrm handler.PrmContainer) (*container.Container, error) { func (x *FrostFS) Container(ctx context.Context, containerPrm layer.PrmContainer) (*container.Container, error) {
prm := pool.PrmContainerGet{ prm := pool.PrmContainerGet{
ContainerID: containerPrm.ContainerID, ContainerID: containerPrm.ContainerID,
} }
@ -47,7 +47,7 @@ func (x *FrostFS) Container(ctx context.Context, containerPrm handler.PrmContain
} }
// CreateObject implements frostfs.FrostFS interface method. // CreateObject implements frostfs.FrostFS interface method.
func (x *FrostFS) CreateObject(ctx context.Context, prm handler.PrmObjectCreate) (oid.ID, error) { func (x *FrostFS) CreateObject(ctx context.Context, prm layer.PrmObjectCreate) (oid.ID, error) {
var prmPut pool.PrmObjectPut var prmPut pool.PrmObjectPut
prmPut.SetHeader(*prm.Object) prmPut.SetHeader(*prm.Object)
prmPut.SetPayload(prm.Payload) prmPut.SetPayload(prm.Payload)
@ -81,7 +81,7 @@ func (x payloadReader) Read(p []byte) (int, error) {
} }
// HeadObject implements frostfs.FrostFS interface method. // HeadObject implements frostfs.FrostFS interface method.
func (x *FrostFS) HeadObject(ctx context.Context, prm handler.PrmObjectHead) (*object.Object, error) { func (x *FrostFS) HeadObject(ctx context.Context, prm layer.PrmObjectHead) (*object.Object, error) {
var prmHead pool.PrmObjectHead var prmHead pool.PrmObjectHead
prmHead.SetAddress(prm.Address) prmHead.SetAddress(prm.Address)
@ -98,7 +98,7 @@ func (x *FrostFS) HeadObject(ctx context.Context, prm handler.PrmObjectHead) (*o
} }
// GetObject implements frostfs.FrostFS interface method. // GetObject implements frostfs.FrostFS interface method.
func (x *FrostFS) GetObject(ctx context.Context, prm handler.PrmObjectGet) (*handler.Object, error) { func (x *FrostFS) GetObject(ctx context.Context, prm layer.PrmObjectGet) (*layer.Object, error) {
var prmGet pool.PrmObjectGet var prmGet pool.PrmObjectGet
prmGet.SetAddress(prm.Address) prmGet.SetAddress(prm.Address)
@ -111,14 +111,14 @@ func (x *FrostFS) GetObject(ctx context.Context, prm handler.PrmObjectGet) (*han
return nil, handleObjectError("init full object reading via connection pool", err) return nil, handleObjectError("init full object reading via connection pool", err)
} }
return &handler.Object{ return &layer.Object{
Header: res.Header, Header: res.Header,
Payload: res.Payload, Payload: res.Payload,
}, nil }, nil
} }
// RangeObject implements frostfs.FrostFS interface method. // RangeObject implements frostfs.FrostFS interface method.
func (x *FrostFS) RangeObject(ctx context.Context, prm handler.PrmObjectRange) (io.ReadCloser, error) { func (x *FrostFS) RangeObject(ctx context.Context, prm layer.PrmObjectRange) (io.ReadCloser, error) {
var prmRange pool.PrmObjectRange var prmRange pool.PrmObjectRange
prmRange.SetAddress(prm.Address) prmRange.SetAddress(prm.Address)
prmRange.SetOffset(prm.PayloadRange[0]) prmRange.SetOffset(prm.PayloadRange[0])
@ -137,7 +137,7 @@ func (x *FrostFS) RangeObject(ctx context.Context, prm handler.PrmObjectRange) (
} }
// SearchObjects implements frostfs.FrostFS interface method. // SearchObjects implements frostfs.FrostFS interface method.
func (x *FrostFS) SearchObjects(ctx context.Context, prm handler.PrmObjectSearch) (handler.ResObjectSearch, error) { func (x *FrostFS) SearchObjects(ctx context.Context, prm layer.PrmObjectSearch) (layer.ResObjectSearch, error) {
var prmSearch pool.PrmObjectSearch var prmSearch pool.PrmObjectSearch
prmSearch.SetContainerID(prm.Container) prmSearch.SetContainerID(prm.Container)
prmSearch.SetFilters(prm.Filters) prmSearch.SetFilters(prm.Filters)
@ -205,11 +205,14 @@ func handleObjectError(msg string, err error) error {
} }
if reason, ok := IsErrObjectAccessDenied(err); ok { if reason, ok := IsErrObjectAccessDenied(err); ok {
return fmt.Errorf("%s: %w: %s", msg, handler.ErrAccessDenied, reason) if strings.Contains(reason, "limit reached") {
return fmt.Errorf("%s: %w: %s", msg, layer.ErrQuotaLimitReached, reason)
}
return fmt.Errorf("%s: %w: %s", msg, layer.ErrAccessDenied, reason)
} }
if IsTimeoutError(err) { if IsTimeoutError(err) {
return fmt.Errorf("%s: %w: %s", msg, handler.ErrGatewayTimeout, err.Error()) return fmt.Errorf("%s: %w: %s", msg, layer.ErrGatewayTimeout, err.Error())
} }
return fmt.Errorf("%s: %w", msg, err) return fmt.Errorf("%s: %w", msg, err)

View file

@ -0,0 +1,83 @@
package frostfs
import (
"context"
"errors"
"fmt"
"testing"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
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 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"
inputErr := new(apistatus.ObjectAccessDenied)
inputErr.WriteReason(reason)
err := handleObjectError(msg, inputErr)
require.ErrorIs(t, err, layer.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, layer.ErrQuotaLimitReached)
require.Contains(t, err.Error(), reason)
require.Contains(t, err.Error(), msg)
})
t.Run("simple timeout", func(t *testing.T) {
inputErr := errors.New("timeout")
err := handleObjectError(msg, inputErr)
require.ErrorIs(t, err, layer.ErrGatewayTimeout)
require.Contains(t, err.Error(), inputErr.Error())
require.Contains(t, err.Error(), msg)
})
t.Run("deadline exceeded", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
<-ctx.Done()
err := handleObjectError(msg, ctx.Err())
require.ErrorIs(t, err, layer.ErrGatewayTimeout)
require.Contains(t, err.Error(), ctx.Err().Error())
require.Contains(t, err.Error(), msg)
})
t.Run("grpc deadline exceeded", func(t *testing.T) {
inputErr := fmt.Errorf("wrap grpc error: %w", status.Error(codes.DeadlineExceeded, "error"))
err := handleObjectError(msg, inputErr)
require.ErrorIs(t, err, layer.ErrGatewayTimeout)
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)
})
}

View file

@ -8,7 +8,7 @@ import (
"io" "io"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
) )
@ -73,9 +73,9 @@ var (
errorZeroRangeLength = errors.New("zero range length") errorZeroRangeLength = errors.New("zero range length")
) )
func (x *FrostFS) InitMultiObjectReader(ctx context.Context, p handler.PrmInitMultiObjectReader) (io.Reader, error) { func (x *FrostFS) InitMultiObjectReader(ctx context.Context, p layer.PrmInitMultiObjectReader) (io.Reader, error) {
combinedObj, err := x.GetObject(ctx, handler.PrmObjectGet{ combinedObj, err := x.GetObject(ctx, layer.PrmObjectGet{
PrmAuth: handler.PrmAuth{BearerToken: p.Bearer}, PrmAuth: layer.PrmAuth{BearerToken: p.Bearer},
Address: p.Addr, Address: p.Addr,
}) })
if err != nil { if err != nil {
@ -215,10 +215,10 @@ func (x *MultiObjectReader) Read(p []byte) (n int, err error) {
// InitFrostFSObjectPayloadReader initializes payload reader of the FrostFS object. // InitFrostFSObjectPayloadReader initializes payload reader of the FrostFS object.
// Zero range corresponds to full payload (panics if only offset is set). // Zero range corresponds to full payload (panics if only offset is set).
func (x *FrostFS) InitFrostFSObjectPayloadReader(ctx context.Context, p GetFrostFSParams) (io.ReadCloser, error) { func (x *FrostFS) InitFrostFSObjectPayloadReader(ctx context.Context, p GetFrostFSParams) (io.ReadCloser, error) {
var prmAuth handler.PrmAuth var prmAuth layer.PrmAuth
if p.Off+p.Ln != 0 { if p.Off+p.Ln != 0 {
prm := handler.PrmObjectRange{ prm := layer.PrmObjectRange{
PrmAuth: prmAuth, PrmAuth: prmAuth,
PayloadRange: [2]uint64{p.Off, p.Ln}, PayloadRange: [2]uint64{p.Off, p.Ln},
Address: p.Addr, Address: p.Addr,
@ -227,7 +227,7 @@ func (x *FrostFS) InitFrostFSObjectPayloadReader(ctx context.Context, p GetFrost
return x.RangeObject(ctx, prm) return x.RangeObject(ctx, prm)
} }
prm := handler.PrmObjectGet{ prm := layer.PrmObjectGet{
PrmAuth: prmAuth, PrmAuth: prmAuth,
Address: p.Addr, Address: p.Addr,
} }

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
sdkstatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" sdkstatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
@ -29,6 +30,9 @@ func FormErrorResponse(message string, err error) (int, string, []zap.Field) {
reason := st.Reason() reason := st.Reason()
msg = fmt.Sprintf("%s: %v: %s", message, err, reason) msg = fmt.Sprintf("%s: %v: %s", message, err, reason)
logFields = append(logFields, zap.String("error_detail", reason)) logFields = append(logFields, zap.String("error_detail", reason))
case errors.Is(err, layer.ErrQuotaLimitReached):
statusCode = fasthttp.StatusConflict
msg = fmt.Sprintf("%s: %v", message, err)
case client.IsErrObjectNotFound(err) || client.IsErrContainerNotFound(err): case client.IsErrObjectNotFound(err) || client.IsErrContainerNotFound(err):
statusCode = fasthttp.StatusNotFound statusCode = fasthttp.StatusNotFound
msg = "Not Found" msg = "Not Found"