2022-03-01 15:07:15 +00:00
|
|
|
package handler
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
2023-08-28 11:06:46 +00:00
|
|
|
"crypto/rand"
|
2022-03-01 15:07:15 +00:00
|
|
|
"encoding/xml"
|
2022-06-01 14:50:30 +00:00
|
|
|
"io"
|
2022-03-01 15:07:15 +00:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2022-06-15 12:17:29 +00:00
|
|
|
"net/url"
|
2022-06-01 14:50:30 +00:00
|
|
|
"strconv"
|
2022-03-01 15:07:15 +00:00
|
|
|
"testing"
|
2022-06-01 14:50:30 +00:00
|
|
|
"time"
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2023-06-29 12:46:42 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
2023-03-07 14:38:08 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
2023-07-05 14:05:45 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
2023-03-07 14:38:08 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
|
2023-03-14 14:31:15 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
|
2023-08-03 12:08:22 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
2023-03-07 14:38:08 +00:00
|
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
|
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
2022-03-04 13:07:27 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
2022-03-01 15:07:15 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2022-04-07 14:36:44 +00:00
|
|
|
"go.uber.org/zap"
|
2022-03-01 15:07:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type handlerContext struct {
|
2022-10-04 08:31:09 +00:00
|
|
|
owner user.ID
|
|
|
|
t *testing.T
|
2022-06-24 12:39:30 +00:00
|
|
|
h *handler
|
2022-12-20 08:38:58 +00:00
|
|
|
tp *layer.TestFrostFS
|
2023-04-14 12:36:22 +00:00
|
|
|
tree *tree.Tree
|
2022-06-24 12:39:30 +00:00
|
|
|
context context.Context
|
2023-09-08 11:17:14 +00:00
|
|
|
config *configMock
|
2023-08-21 13:05:16 +00:00
|
|
|
|
|
|
|
layerFeatures *layer.FeatureSettingsMock
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (hc *handlerContext) Handler() *handler {
|
|
|
|
return hc.h
|
|
|
|
}
|
|
|
|
|
2022-12-20 08:38:58 +00:00
|
|
|
func (hc *handlerContext) MockedPool() *layer.TestFrostFS {
|
2022-03-01 15:07:15 +00:00
|
|
|
return hc.tp
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hc *handlerContext) Layer() layer.Client {
|
|
|
|
return hc.h.obj
|
|
|
|
}
|
|
|
|
|
2022-06-24 12:39:30 +00:00
|
|
|
func (hc *handlerContext) Context() context.Context {
|
|
|
|
return hc.context
|
|
|
|
}
|
|
|
|
|
2023-09-08 11:17:14 +00:00
|
|
|
type configMock struct {
|
|
|
|
defaultPolicy netmap.PlacementPolicy
|
|
|
|
copiesNumbers map[string][]uint32
|
|
|
|
defaultCopiesNumbers []uint32
|
|
|
|
bypassContentEncodingInChunks bool
|
2022-11-14 09:37:47 +00:00
|
|
|
}
|
|
|
|
|
2023-09-08 11:17:14 +00:00
|
|
|
func (c *configMock) DefaultPlacementPolicy() netmap.PlacementPolicy {
|
|
|
|
return c.defaultPolicy
|
2022-11-14 09:37:47 +00:00
|
|
|
}
|
|
|
|
|
2023-09-08 11:17:14 +00:00
|
|
|
func (c *configMock) PlacementPolicy(string) (netmap.PlacementPolicy, bool) {
|
2022-11-14 09:37:47 +00:00
|
|
|
return netmap.PlacementPolicy{}, false
|
|
|
|
}
|
|
|
|
|
2023-09-08 11:17:14 +00:00
|
|
|
func (c *configMock) CopiesNumbers(locationConstraint string) ([]uint32, bool) {
|
|
|
|
result, ok := c.copiesNumbers[locationConstraint]
|
2023-05-18 13:20:00 +00:00
|
|
|
return result, ok
|
|
|
|
}
|
|
|
|
|
2023-09-08 11:17:14 +00:00
|
|
|
func (c *configMock) DefaultCopiesNumbers() []uint32 {
|
|
|
|
return c.defaultCopiesNumbers
|
2023-05-18 13:20:00 +00:00
|
|
|
}
|
|
|
|
|
2023-09-08 11:17:14 +00:00
|
|
|
func (c *configMock) NewCompleteMultipartDecoder(r io.Reader) *xml.Decoder {
|
2023-03-02 14:54:33 +00:00
|
|
|
return xml.NewDecoder(r)
|
|
|
|
}
|
|
|
|
|
2023-09-08 11:17:14 +00:00
|
|
|
func (c *configMock) BypassContentEncodingInChunks() bool {
|
|
|
|
return c.bypassContentEncodingInChunks
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *configMock) DefaultMaxAge() int {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *configMock) NotificatorEnabled() bool {
|
|
|
|
return false
|
2023-07-10 09:17:44 +00:00
|
|
|
}
|
|
|
|
|
2023-09-08 11:17:14 +00:00
|
|
|
func (c *configMock) ResolveZoneList() []string {
|
|
|
|
return []string{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *configMock) IsResolveListAllow() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *configMock) CompleteMultipartKeepalive() time.Duration {
|
|
|
|
return time.Duration(0)
|
2023-07-10 09:17:44 +00:00
|
|
|
}
|
|
|
|
|
2022-03-01 15:07:15 +00:00
|
|
|
func prepareHandlerContext(t *testing.T) *handlerContext {
|
2023-06-29 12:46:42 +00:00
|
|
|
return prepareHandlerContextBase(t, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func prepareHandlerContextWithMinCache(t *testing.T) *handlerContext {
|
|
|
|
return prepareHandlerContextBase(t, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func prepareHandlerContextBase(t *testing.T, minCache bool) *handlerContext {
|
2022-03-01 15:07:15 +00:00
|
|
|
key, err := keys.NewPrivateKey()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-06-15 12:17:29 +00:00
|
|
|
l := zap.NewExample()
|
2023-08-03 12:08:22 +00:00
|
|
|
tp := layer.NewTestFrostFS(key)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-09-12 13:46:55 +00:00
|
|
|
testResolver := &resolver.Resolver{Name: "test_resolver"}
|
2022-06-27 09:08:26 +00:00
|
|
|
testResolver.SetResolveFunc(func(_ context.Context, name string) (cid.ID, error) {
|
2022-03-04 13:07:27 +00:00
|
|
|
return tp.ContainerID(name)
|
2022-03-01 15:07:15 +00:00
|
|
|
})
|
|
|
|
|
2022-10-04 08:31:09 +00:00
|
|
|
var owner user.ID
|
|
|
|
user.IDFromKey(&owner, key.PrivateKey.PublicKey)
|
|
|
|
|
2023-04-14 12:36:22 +00:00
|
|
|
treeMock := NewTreeServiceMock(t)
|
|
|
|
|
2023-06-29 12:46:42 +00:00
|
|
|
cacheCfg := layer.DefaultCachesConfigs(l)
|
|
|
|
if minCache {
|
|
|
|
cacheCfg = getMinCacheConfig(l)
|
|
|
|
}
|
|
|
|
|
2023-08-21 13:05:16 +00:00
|
|
|
features := &layer.FeatureSettingsMock{}
|
|
|
|
|
2022-03-01 15:07:15 +00:00
|
|
|
layerCfg := &layer.Config{
|
2023-06-29 12:46:42 +00:00
|
|
|
Caches: cacheCfg,
|
2022-05-20 08:26:35 +00:00
|
|
|
AnonKey: layer.AnonymousKey{Key: key},
|
|
|
|
Resolver: testResolver,
|
2023-04-14 12:36:22 +00:00
|
|
|
TreeService: treeMock,
|
2023-08-21 13:05:16 +00:00
|
|
|
Features: features,
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
|
|
|
|
2022-11-14 09:37:47 +00:00
|
|
|
var pp netmap.PlacementPolicy
|
|
|
|
err = pp.DecodeString("REP 1")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-09-08 11:17:14 +00:00
|
|
|
cfg := &configMock{
|
|
|
|
defaultPolicy: pp,
|
|
|
|
}
|
2022-03-01 15:07:15 +00:00
|
|
|
h := &handler{
|
|
|
|
log: l,
|
|
|
|
obj: layer.NewLayer(l, tp, layerCfg),
|
2023-09-08 11:17:14 +00:00
|
|
|
cfg: cfg,
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &handlerContext{
|
2022-10-04 08:31:09 +00:00
|
|
|
owner: owner,
|
|
|
|
t: t,
|
2022-06-24 12:39:30 +00:00
|
|
|
h: h,
|
|
|
|
tp: tp,
|
2023-04-14 12:36:22 +00:00
|
|
|
tree: treeMock,
|
2023-08-14 15:34:41 +00:00
|
|
|
context: middleware.SetBoxData(context.Background(), newTestAccessBox(t, key)),
|
2023-09-08 11:17:14 +00:00
|
|
|
config: cfg,
|
2023-08-21 13:05:16 +00:00
|
|
|
|
|
|
|
layerFeatures: features,
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-29 12:46:42 +00:00
|
|
|
func getMinCacheConfig(logger *zap.Logger) *layer.CachesConfig {
|
|
|
|
minCacheCfg := &cache.Config{
|
|
|
|
Size: 1,
|
|
|
|
Lifetime: 1,
|
|
|
|
Logger: logger,
|
|
|
|
}
|
|
|
|
return &layer.CachesConfig{
|
|
|
|
Logger: logger,
|
|
|
|
Objects: minCacheCfg,
|
|
|
|
ObjectsList: minCacheCfg,
|
|
|
|
Names: minCacheCfg,
|
|
|
|
Buckets: minCacheCfg,
|
|
|
|
System: minCacheCfg,
|
|
|
|
AccessControl: minCacheCfg,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-14 14:31:15 +00:00
|
|
|
func NewTreeServiceMock(t *testing.T) *tree.Tree {
|
|
|
|
memCli, err := tree.NewTreeServiceClientMemory()
|
|
|
|
require.NoError(t, err)
|
2023-04-03 14:26:17 +00:00
|
|
|
return tree.NewTree(memCli, zap.NewExample())
|
2023-03-14 14:31:15 +00:00
|
|
|
}
|
|
|
|
|
2022-10-04 08:31:09 +00:00
|
|
|
func createTestBucket(hc *handlerContext, bktName string) *data.BucketInfo {
|
|
|
|
_, err := hc.MockedPool().CreateContainer(hc.Context(), layer.PrmContainerCreate{
|
2023-08-03 12:08:22 +00:00
|
|
|
Creator: hc.owner,
|
|
|
|
Name: bktName,
|
|
|
|
BasicACL: acl.PublicRWExtended,
|
2022-03-04 13:07:27 +00:00
|
|
|
})
|
2022-10-04 08:31:09 +00:00
|
|
|
require.NoError(hc.t, err)
|
2022-08-01 16:52:09 +00:00
|
|
|
|
2022-10-04 08:31:09 +00:00
|
|
|
bktInfo, err := hc.Layer().GetBucketInfo(hc.Context(), bktName)
|
|
|
|
require.NoError(hc.t, err)
|
2022-08-01 16:52:09 +00:00
|
|
|
return bktInfo
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
|
|
|
|
2022-10-04 08:31:09 +00:00
|
|
|
func createTestBucketWithLock(hc *handlerContext, bktName string, conf *data.ObjectLockConfiguration) *data.BucketInfo {
|
|
|
|
cnrID, err := hc.MockedPool().CreateContainer(hc.Context(), layer.PrmContainerCreate{
|
|
|
|
Creator: hc.owner,
|
2022-03-04 13:07:27 +00:00
|
|
|
Name: bktName,
|
|
|
|
AdditionalAttributes: [][2]string{{layer.AttributeLockEnabled, "true"}},
|
|
|
|
})
|
2022-10-04 08:31:09 +00:00
|
|
|
require.NoError(hc.t, err)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-04-25 09:57:58 +00:00
|
|
|
var ownerID user.ID
|
|
|
|
|
2022-03-18 13:04:09 +00:00
|
|
|
bktInfo := &data.BucketInfo{
|
2022-06-27 09:08:26 +00:00
|
|
|
CID: cnrID,
|
2022-03-18 13:04:09 +00:00
|
|
|
Name: bktName,
|
|
|
|
ObjectLockEnabled: true,
|
2022-05-25 17:25:43 +00:00
|
|
|
Owner: ownerID,
|
2022-03-18 13:04:09 +00:00
|
|
|
}
|
|
|
|
|
2022-03-01 15:07:15 +00:00
|
|
|
sp := &layer.PutSettingsParams{
|
2022-03-18 13:04:09 +00:00
|
|
|
BktInfo: bktInfo,
|
2022-03-01 15:07:15 +00:00
|
|
|
Settings: &data.BucketSettings{
|
2022-07-20 10:30:19 +00:00
|
|
|
Versioning: data.VersioningEnabled,
|
2022-03-01 15:07:15 +00:00
|
|
|
LockConfiguration: conf,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-10-04 08:31:09 +00:00
|
|
|
err = hc.Layer().PutBucketSettings(hc.Context(), sp)
|
|
|
|
require.NoError(hc.t, err)
|
2022-03-18 13:04:09 +00:00
|
|
|
|
|
|
|
return bktInfo
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
|
|
|
|
2022-10-04 08:31:09 +00:00
|
|
|
func createTestObject(hc *handlerContext, bktInfo *data.BucketInfo, objName string) *data.ObjectInfo {
|
2022-03-01 15:07:15 +00:00
|
|
|
content := make([]byte, 1024)
|
|
|
|
_, err := rand.Read(content)
|
2022-10-04 08:31:09 +00:00
|
|
|
require.NoError(hc.t, err)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-06-01 14:50:30 +00:00
|
|
|
header := map[string]string{
|
|
|
|
object.AttributeTimestamp: strconv.FormatInt(time.Now().UTC().Unix(), 10),
|
|
|
|
}
|
|
|
|
|
2022-10-14 14:36:43 +00:00
|
|
|
extObjInfo, err := hc.Layer().PutObject(hc.Context(), &layer.PutObjectParams{
|
2022-03-18 13:04:09 +00:00
|
|
|
BktInfo: bktInfo,
|
|
|
|
Object: objName,
|
2023-06-01 13:45:28 +00:00
|
|
|
Size: uint64(len(content)),
|
2022-03-18 13:04:09 +00:00
|
|
|
Reader: bytes.NewReader(content),
|
2022-06-01 14:50:30 +00:00
|
|
|
Header: header,
|
2022-03-01 15:07:15 +00:00
|
|
|
})
|
2022-10-04 08:31:09 +00:00
|
|
|
require.NoError(hc.t, err)
|
2022-06-15 12:17:29 +00:00
|
|
|
|
2022-10-14 14:36:43 +00:00
|
|
|
return extObjInfo.ObjectInfo
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
|
|
|
|
2022-10-04 08:31:09 +00:00
|
|
|
func prepareTestRequest(hc *handlerContext, bktName, objName string, body interface{}) (*httptest.ResponseRecorder, *http.Request) {
|
|
|
|
return prepareTestFullRequest(hc, bktName, objName, make(url.Values), body)
|
2022-06-15 12:17:29 +00:00
|
|
|
}
|
|
|
|
|
2022-10-04 08:31:09 +00:00
|
|
|
func prepareTestFullRequest(hc *handlerContext, bktName, objName string, query url.Values, body interface{}) (*httptest.ResponseRecorder, *http.Request) {
|
2022-03-01 15:07:15 +00:00
|
|
|
rawBody, err := xml.Marshal(body)
|
2022-10-04 08:31:09 +00:00
|
|
|
require.NoError(hc.t, err)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-10-04 08:31:09 +00:00
|
|
|
return prepareTestRequestWithQuery(hc, bktName, objName, query, rawBody)
|
2022-08-01 16:52:09 +00:00
|
|
|
}
|
|
|
|
|
2022-10-04 08:31:09 +00:00
|
|
|
func prepareTestRequestWithQuery(hc *handlerContext, bktName, objName string, query url.Values, body []byte) (*httptest.ResponseRecorder, *http.Request) {
|
2022-03-01 15:07:15 +00:00
|
|
|
w := httptest.NewRecorder()
|
2022-08-01 16:52:09 +00:00
|
|
|
r := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(body))
|
2022-06-15 12:17:29 +00:00
|
|
|
r.URL.RawQuery = query.Encode()
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2023-07-05 14:05:45 +00:00
|
|
|
reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: bktName, Object: objName})
|
|
|
|
r = r.WithContext(middleware.SetReqInfo(hc.Context(), reqInfo))
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
return w, r
|
|
|
|
}
|
2022-06-01 14:50:30 +00:00
|
|
|
|
2022-10-04 08:31:09 +00:00
|
|
|
func prepareTestPayloadRequest(hc *handlerContext, bktName, objName string, payload io.Reader) (*httptest.ResponseRecorder, *http.Request) {
|
2022-06-01 09:54:27 +00:00
|
|
|
w := httptest.NewRecorder()
|
|
|
|
r := httptest.NewRequest(http.MethodPut, defaultURL, payload)
|
|
|
|
|
2023-07-05 14:05:45 +00:00
|
|
|
reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: bktName, Object: objName})
|
|
|
|
r = r.WithContext(middleware.SetReqInfo(hc.Context(), reqInfo))
|
2022-06-01 09:54:27 +00:00
|
|
|
|
|
|
|
return w, r
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseTestResponse(t *testing.T, response *httptest.ResponseRecorder, body interface{}) {
|
|
|
|
assertStatus(t, response, http.StatusOK)
|
|
|
|
err := xml.NewDecoder(response.Result().Body).Decode(body)
|
|
|
|
require.NoError(t, err)
|
2022-06-01 14:50:30 +00:00
|
|
|
}
|
2022-06-24 12:39:30 +00:00
|
|
|
|
2022-12-20 08:38:58 +00:00
|
|
|
func existInMockedFrostFS(tc *handlerContext, bktInfo *data.BucketInfo, objInfo *data.ObjectInfo) bool {
|
2022-06-24 12:39:30 +00:00
|
|
|
p := &layer.GetObjectParams{
|
|
|
|
BucketInfo: bktInfo,
|
|
|
|
ObjectInfo: objInfo,
|
|
|
|
}
|
|
|
|
|
2023-07-06 13:37:53 +00:00
|
|
|
objPayload, err := tc.Layer().GetObject(tc.Context(), p)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = io.ReadAll(objPayload)
|
|
|
|
require.NoError(tc.t, err)
|
|
|
|
return true
|
2022-06-24 12:39:30 +00:00
|
|
|
}
|
|
|
|
|
2022-12-20 08:38:58 +00:00
|
|
|
func listOIDsFromMockedFrostFS(t *testing.T, tc *handlerContext, bktName string) []oid.ID {
|
2022-06-24 12:39:30 +00:00
|
|
|
bktInfo, err := tc.Layer().GetBucketInfo(tc.Context(), bktName)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-08-22 08:02:58 +00:00
|
|
|
return tc.MockedPool().AllObjects(bktInfo.CID)
|
2022-06-24 12:39:30 +00:00
|
|
|
}
|
2022-08-01 16:52:09 +00:00
|
|
|
|
|
|
|
func assertStatus(t *testing.T, w *httptest.ResponseRecorder, status int) {
|
|
|
|
if w.Code != status {
|
|
|
|
resp, err := io.ReadAll(w.Result().Body)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Failf(t, "unexpected status", "expected: %d, actual: %d, resp: '%s'", status, w.Code, string(resp))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func readResponse(t *testing.T, w *httptest.ResponseRecorder, status int, model interface{}) {
|
|
|
|
assertStatus(t, w, status)
|
2023-09-15 15:08:02 +00:00
|
|
|
if status == http.StatusOK {
|
|
|
|
err := xml.NewDecoder(w.Result().Body).Decode(model)
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
2022-08-01 16:52:09 +00:00
|
|
|
}
|