Compare commits

..

7 commits

Author SHA1 Message Date
d0e1154535 Release v0.28.2
Some checks failed
/ DCO (pull_request) Successful in 1m38s
/ Builds (1.20) (pull_request) Successful in 2m9s
/ Builds (1.21) (pull_request) Successful in 1m36s
/ Vulncheck (pull_request) Failing after 2m15s
/ Lint (pull_request) Successful in 4m22s
/ Tests (1.20) (pull_request) Successful in 2m33s
/ Tests (1.21) (pull_request) Successful in 2m1s
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2024-05-27 12:32:11 +03:00
53fa78d63f [#370] go.mod: Fix dependencies
Some checks failed
/ Builds (1.20) (pull_request) Successful in 1m51s
/ Builds (1.21) (pull_request) Successful in 1m38s
/ Vulncheck (pull_request) Failing after 1m51s
/ Lint (pull_request) Successful in 3m46s
/ Tests (1.20) (pull_request) Successful in 2m22s
/ Tests (1.21) (pull_request) Successful in 2m15s
/ DCO (pull_request) Successful in 44s
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2024-04-23 10:23:06 +03:00
5d0b84acf9 [#370] Fix fetching attributes from tree
Some checks failed
/ DCO (pull_request) Successful in 1m19s
/ Vulncheck (pull_request) Failing after 1m43s
/ Builds (1.20) (pull_request) Successful in 2m14s
/ Builds (1.21) (pull_request) Successful in 2m4s
/ Lint (pull_request) Successful in 3m3s
/ Tests (1.20) (pull_request) Successful in 2m22s
/ Tests (1.21) (pull_request) Successful in 2m10s
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2024-04-19 17:26:13 +03:00
3f1ebde426 [#370] Fix multipart upload size in tree node
Some checks failed
/ DCO (pull_request) Successful in 2m24s
/ Vulncheck (pull_request) Failing after 2m36s
/ Builds (1.20) (pull_request) Successful in 3m14s
/ Builds (1.21) (pull_request) Successful in 3m8s
/ Lint (pull_request) Successful in 5m23s
/ Tests (1.20) (pull_request) Successful in 3m13s
/ Tests (1.21) (pull_request) Successful in 2m58s
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2024-04-17 14:35:18 +03:00
399a6d6d65 [#370] Fix removing combined object
Port #364

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2024-04-17 14:35:12 +03:00
486b8a4284 [#321] Use correct owner id in billing metrics
All checks were successful
/ DCO (pull_request) Successful in 1m23s
/ Vulncheck (pull_request) Successful in 2m7s
/ Builds (1.20) (pull_request) Successful in 2m38s
/ Builds (1.21) (pull_request) Successful in 1m41s
/ Lint (pull_request) Successful in 2m57s
/ Tests (1.20) (pull_request) Successful in 2m22s
/ Tests (1.21) (pull_request) Successful in 2m14s
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2024-02-27 11:23:55 +03:00
94980059b7 [#280] Add put requests to duration metric
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2024-01-29 06:28:04 +00:00
13 changed files with 176 additions and 31 deletions

View file

@ -4,6 +4,15 @@ This document outlines major changes between releases.
## [Unreleased]
## [0.28.2] - 2024-05-27
### Fixed
- `anon` user in billing metrics (#321)
- Parts are not removed when multipart object removed (#370)
### Added
- Put request in duration metrics (#280)
## [0.28.1] - 2024-01-24
### Added
@ -120,4 +129,5 @@ To see CHANGELOG for older versions, refer to https://github.com/nspcc-dev/neofs
[0.27.0]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/b2148cc3...v0.27.0
[0.28.0]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.27.0...v0.28.0
[0.28.1]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.28.0...v0.28.1
[Unreleased]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.28.1...master
[0.28.2]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.28.1...v0.28.2
[Unreleased]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.28.2...master

View file

@ -1 +1 @@
v0.28.1
v0.28.2

View file

@ -62,6 +62,52 @@ func TestPeriodicWriter(t *testing.T) {
})
}
func TestDeleteMultipartAllParts(t *testing.T) {
hc := prepareHandlerContext(t)
partSize := layer.UploadMinSize
objLen := 6 * partSize
bktName, bktName2, objName := "bucket", "bucket2", "object"
// unversioned bucket
createTestBucket(hc, bktName)
multipartUpload(hc, bktName, objName, nil, objLen, partSize)
deleteObject(t, hc, bktName, objName, emptyVersion)
require.Empty(t, hc.tp.Objects())
// encrypted multipart
multipartUploadEncrypted(hc, bktName, objName, nil, objLen, partSize)
deleteObject(t, hc, bktName, objName, emptyVersion)
require.Empty(t, hc.tp.Objects())
// versions bucket
createTestBucket(hc, bktName2)
putBucketVersioning(t, hc, bktName2, true)
multipartUpload(hc, bktName2, objName, nil, objLen, partSize)
_, hdr := getObject(hc, bktName2, objName)
versionID := hdr.Get("X-Amz-Version-Id")
deleteObject(t, hc, bktName2, objName, emptyVersion)
deleteObject(t, hc, bktName2, objName, versionID)
require.Empty(t, hc.tp.Objects())
}
func TestMultipartTreeSize(t *testing.T) {
hc := prepareHandlerContext(t)
partSize := layer.UploadMinSize
objLen := 6 * partSize
bktName, objName := "bucket", "object"
bktInfo := createTestBucket(hc, bktName)
multipartUpload(hc, bktName, objName, nil, objLen, partSize)
nodeVersion, err := hc.tree.GetLatestVersion(hc.Context(), bktInfo, objName)
require.NoError(t, err)
require.EqualValues(t, objLen, nodeVersion.Size)
}
func TestMultipartUploadInvalidPart(t *testing.T) {
hc := prepareHandlerContext(t)

View file

@ -4,6 +4,7 @@ import (
"context"
"crypto/ecdsa"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"net/url"
@ -752,9 +753,40 @@ func (n *layer) removeOldVersion(ctx context.Context, bkt *data.BucketInfo, node
return obj.VersionID, nil
}
if nodeVersion.IsCombined {
return "", n.removeCombinedObject(ctx, bkt, nodeVersion)
}
return "", n.objectDelete(ctx, bkt, nodeVersion.OID)
}
func (n *layer) removeCombinedObject(ctx context.Context, bkt *data.BucketInfo, nodeVersion *data.NodeVersion) error {
combinedObj, err := n.objectGet(ctx, bkt, nodeVersion.OID)
if err != nil {
return fmt.Errorf("get combined object '%s': %w", nodeVersion.OID.EncodeToString(), err)
}
var parts []*data.PartInfo
if err = json.Unmarshal(combinedObj.Payload(), &parts); err != nil {
return fmt.Errorf("unmarshal combined object parts: %w", err)
}
for _, part := range parts {
if err = n.objectDelete(ctx, bkt, part.OID); err == nil {
continue
}
if !client.IsErrObjectAlreadyRemoved(err) && !client.IsErrObjectNotFound(err) {
return fmt.Errorf("couldn't delete part '%s': %w", part.OID.EncodeToString(), err)
}
n.reqLogger(ctx).Warn(logs.CouldntDeletePart, zap.String("cid", bkt.CID.EncodeToString()),
zap.String("oid", part.OID.EncodeToString()), zap.Int("part number", part.Number), zap.Error(err))
}
return n.objectDelete(ctx, bkt, nodeVersion.OID)
}
// DeleteObjects from the storage.
func (n *layer) DeleteObjects(ctx context.Context, p *DeleteObjectParams) []*VersionedObject {
for i, obj := range p.Objects {

View file

@ -315,7 +315,7 @@ func (n *layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.Extend
OID: id,
ETag: hex.EncodeToString(hash),
FilePath: p.Object,
Size: size,
Size: p.Size,
},
IsUnversioned: !bktSettings.VersioningEnabled(),
IsCombined: p.Header[MultipartObjectSize] != "",
@ -356,7 +356,7 @@ func (n *layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.Extend
Owner: n.gateOwner,
Bucket: p.BktInfo.Name,
Name: p.Object,
Size: size,
Size: size, // we don't use here p.Size to be consistent with the objectInfoFromMeta function
Created: prm.CreationTime,
Headers: p.Header,
ContentType: p.Header[api.ContentType],

View file

@ -6,6 +6,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
"go.uber.org/zap"
)
@ -13,6 +14,8 @@ func Auth(center auth.Center, log *zap.Logger) Func {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
reqInfo := GetReqInfo(ctx)
reqInfo.User = "anon"
box, err := center.Authenticate(r)
if err != nil {
if err == auth.ErrNoAuthorizationHeader {
@ -31,6 +34,10 @@ func Auth(center auth.Center, log *zap.Logger) Func {
ctx = SetClientTime(ctx, box.ClientTime)
}
ctx = SetAuthHeaders(ctx, box.AuthHeaders)
if box.AccessBox.Gate.BearerToken != nil {
reqInfo.User = bearer.ResolveIssuer(*box.AccessBox.Gate.BearerToken).String()
}
}
h.ServeHTTP(w, r.WithContext(ctx))

View file

@ -12,7 +12,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
"go.uber.org/zap"
)
@ -80,9 +79,8 @@ func stats(f http.HandlerFunc, resolveCID cidResolveFunc, appMetrics *metrics.Ap
// simply for the fact that it is not human-readable.
durationSecs := time.Since(statsWriter.startTime).Seconds()
user := resolveUser(r.Context())
cnrID := resolveCID(r.Context(), reqInfo)
appMetrics.Update(user, reqInfo.BucketName, cnrID, requestTypeFromAPI(reqInfo.API), in.countBytes, out.countBytes)
appMetrics.Update(reqInfo.User, reqInfo.BucketName, cnrID, requestTypeFromAPI(reqInfo.API), in.countBytes, out.countBytes)
code := statsWriter.statusCode
// A successful request has a 2xx response code
@ -94,10 +92,8 @@ func stats(f http.HandlerFunc, resolveCID cidResolveFunc, appMetrics *metrics.Ap
}
}
if r.Method == http.MethodGet {
// Increment the prometheus http request response histogram with appropriate label
appMetrics.Statistic().RequestDurationsUpdate(reqInfo.API, durationSecs)
}
// Increment the prometheus http request response histogram with appropriate label
appMetrics.Statistic().RequestDurationsUpdate(reqInfo.API, durationSecs)
appMetrics.Statistic().TotalInputBytesAdd(in.countBytes)
appMetrics.Statistic().TotalOutputBytesAdd(out.countBytes)
@ -150,14 +146,6 @@ func resolveCID(log *zap.Logger, resolveBucket BucketResolveFunc) cidResolveFunc
}
}
func resolveUser(ctx context.Context) string {
user := "anon"
if bd, err := GetBoxData(ctx); err == nil && bd.Gate.BearerToken != nil {
user = bearer.ResolveIssuer(*bd.Gate.BearerToken).String()
}
return user
}
// WriteHeader -- writes http status code.
func (w *responseWrapper) WriteHeader(code int) {
w.Do(func() {

View file

@ -36,6 +36,7 @@ type (
BucketName string // Bucket name
ObjectName string // Object name
TraceID string // Trace ID
User string // User owner id
URL *url.URL // Request url
tags []KeyVal // Any additional info not accommodated by above fields
}

View file

@ -9,14 +9,34 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
bearertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer/test"
"github.com/stretchr/testify/require"
)
type anonCenterMock struct {
}
func (c *anonCenterMock) Authenticate(*http.Request) (*auth.Box, error) {
return &auth.Box{
AccessBox: &accessbox.Box{
Gate: &accessbox.GateData{},
},
}, nil
}
type centerMock struct {
}
func (c *centerMock) Authenticate(*http.Request) (*auth.Box, error) {
return &auth.Box{}, nil
token := bearertest.Token()
return &auth.Box{
AccessBox: &accessbox.Box{
Gate: &accessbox.GateData{
BearerToken: &token,
},
},
}, nil
}
type handlerMock struct {

View file

@ -10,6 +10,7 @@ import (
"testing"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
@ -18,7 +19,7 @@ import (
)
func TestRouterUploadPart(t *testing.T) {
chiRouter := prepareRouter(t)
chiRouter := prepareRouter(t, &anonCenterMock{})
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodPut, "/dkirillov/fix-object", nil)
@ -33,7 +34,7 @@ func TestRouterUploadPart(t *testing.T) {
}
func TestRouterListMultipartUploads(t *testing.T) {
chiRouter := prepareRouter(t)
chiRouter := prepareRouter(t, &anonCenterMock{})
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/test-bucket", nil)
@ -47,7 +48,7 @@ func TestRouterListMultipartUploads(t *testing.T) {
}
func TestRouterObjectWithSlashes(t *testing.T) {
chiRouter := prepareRouter(t)
chiRouter := prepareRouter(t, &anonCenterMock{})
bktName, objName := "dkirillov", "/fix/object"
target := fmt.Sprintf("/%s/%s", bktName, objName)
@ -62,7 +63,7 @@ func TestRouterObjectWithSlashes(t *testing.T) {
}
func TestRouterObjectEscaping(t *testing.T) {
chiRouter := prepareRouter(t)
chiRouter := prepareRouter(t, &anonCenterMock{})
bktName := "dkirillov"
@ -106,19 +107,38 @@ func TestRouterObjectEscaping(t *testing.T) {
}
}
func prepareRouter(t *testing.T) *chi.Mux {
func TestOwnerIDRetrieving(t *testing.T) {
anonRouter := prepareRouter(t, &anonCenterMock{})
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/test-bucket", nil)
anonRouter.ServeHTTP(w, r)
resp := readResponse(t, w)
require.Equal(t, "anon", resp.ReqInfo.User)
chiRouter := prepareRouter(t, &centerMock{})
w = httptest.NewRecorder()
r = httptest.NewRequest(http.MethodGet, "/test-bucket", nil)
chiRouter.ServeHTTP(w, r)
resp = readResponse(t, w)
require.NotEqual(t, "anon", resp.ReqInfo.User)
}
func prepareRouter(t *testing.T, center auth.Center) *chi.Mux {
throttleOps := middleware.ThrottleOpts{
Limit: 10,
BacklogTimeout: 30 * time.Second,
}
handleMock := &handlerMock{t: t}
cntrMock := &centerMock{}
log := zaptest.NewLogger(t)
metric := &metrics.AppMetrics{}
chiRouter := chi.NewRouter()
AttachChi(chiRouter, nil, throttleOps, handleMock, cntrMock, log, metric)
AttachChi(chiRouter, nil, throttleOps, handleMock, center, log, metric)
return chiRouter
}

2
go.mod
View file

@ -25,6 +25,7 @@ require (
go.opentelemetry.io/otel/trace v1.16.0
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.9.0
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc
google.golang.org/grpc v1.55.0
google.golang.org/protobuf v1.30.0
)
@ -85,7 +86,6 @@ require (
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.8.0 // indirect

View file

@ -565,7 +565,7 @@ func (c *Tree) GetVersions(ctx context.Context, bktInfo *data.BucketInfo, filepa
}
func (c *Tree) GetLatestVersion(ctx context.Context, bktInfo *data.BucketInfo, objectName string) (*data.NodeVersion, error) {
meta := []string{oidKV, isUnversionedKV, isDeleteMarkerKV, etagKV, sizeKV, md5KV}
meta := []string{oidKV, isCombinedKV, isUnversionedKV, isDeleteMarkerKV, etagKV, sizeKV, md5KV}
path := pathFromName(objectName)
p := &GetNodesParams{
@ -1191,7 +1191,7 @@ func (c *Tree) clearOutdatedVersionInfo(ctx context.Context, bktInfo *data.Bucke
}
func (c *Tree) getVersions(ctx context.Context, bktInfo *data.BucketInfo, treeID, filepath string, onlyUnversioned bool) ([]*data.NodeVersion, error) {
keysToReturn := []string{oidKV, isUnversionedKV, isDeleteMarkerKV, etagKV, sizeKV, md5KV}
keysToReturn := []string{oidKV, isCombinedKV, isUnversionedKV, isDeleteMarkerKV, etagKV, sizeKV, md5KV}
path := pathFromName(filepath)
p := &GetNodesParams{
BktInfo: bktInfo,

View file

@ -7,6 +7,7 @@ import (
"time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"golang.org/x/exp/slices"
)
type nodeMeta struct {
@ -183,6 +184,22 @@ func NewTreeServiceClientMemory() (*ServiceClientMemory, error) {
}, nil
}
type nodeResponseWrapper struct {
nodeResponse
allAttr bool
attrs []string
}
func (n nodeResponseWrapper) GetMeta() []Meta {
res := make([]Meta, 0, len(n.meta))
for _, value := range n.meta {
if n.allAttr || slices.Contains(n.attrs, value.key) {
res = append(res, value)
}
}
return res
}
func (c *ServiceClientMemory) GetNodes(_ context.Context, p *GetNodesParams) ([]NodeResponse, error) {
cnr, ok := c.containers[p.BktInfo.CID.EncodeToString()]
if !ok {
@ -205,7 +222,11 @@ func (c *ServiceClientMemory) GetNodes(_ context.Context, p *GetNodesParams) ([]
res2 := make([]NodeResponse, len(res))
for i, n := range res {
res2[i] = n
res2[i] = nodeResponseWrapper{
nodeResponse: n,
allAttr: p.AllAttrs,
attrs: p.Meta,
}
}
return res2, nil