From 971006a28c440e7b19eb29b994b9db3ff85ba7a3 Mon Sep 17 00:00:00 2001 From: Marina Biryukova Date: Mon, 15 Jul 2024 18:35:54 +0300 Subject: [PATCH] [#422] Support separate container for CORS Signed-off-by: Marina Biryukova --- api/data/info.go | 4 ++- api/layer/cache.go | 6 ++-- api/layer/cors.go | 72 ++++++++++++++++++++++++++------------ api/layer/layer.go | 27 +++++++++++++- api/layer/object.go | 20 +++++++++++ api/layer/system_object.go | 20 +++++++---- api/layer/tree_mock.go | 19 +++++----- api/layer/tree_service.go | 6 ++-- cmd/s3-gw/app.go | 50 +++++++++++++++++++++++--- cmd/s3-gw/app_settings.go | 3 ++ config/config.env | 2 ++ config/config.yaml | 4 +++ docs/configuration.md | 13 +++++++ internal/logs/logs.go | 1 + pkg/service/tree/tree.go | 54 +++++++++++++++++++++------- 15 files changed, 240 insertions(+), 61 deletions(-) diff --git a/api/data/info.go b/api/data/info.go index 78a124a..686349b 100644 --- a/api/data/info.go +++ b/api/data/info.go @@ -87,7 +87,9 @@ type ( func (b *BucketInfo) SettingsObjectName() string { return bktSettingsObject } // CORSObjectName returns a system name for a bucket CORS configuration file. -func (b *BucketInfo) CORSObjectName() string { return bktCORSConfigurationObject } +func (b *BucketInfo) CORSObjectName() string { + return b.CID.EncodeToString() + bktCORSConfigurationObject +} // VersionID returns object version from ObjectInfo. func (o *ObjectInfo) VersionID() string { return o.ID.EncodeToString() } diff --git a/api/layer/cache.go b/api/layer/cache.go index 0618dee..71520f2 100644 --- a/api/layer/cache.go +++ b/api/layer/cache.go @@ -233,7 +233,7 @@ func (c *Cache) PutSettings(owner user.ID, bktInfo *data.BucketInfo, settings *d } func (c *Cache) GetCORS(owner user.ID, bkt *data.BucketInfo) *data.CORSConfiguration { - key := bkt.Name + bkt.CORSObjectName() + key := bkt.CORSObjectName() if !c.accessCache.Get(owner, key) { return nil @@ -243,7 +243,7 @@ func (c *Cache) GetCORS(owner user.ID, bkt *data.BucketInfo) *data.CORSConfigura } func (c *Cache) PutCORS(owner user.ID, bkt *data.BucketInfo, cors *data.CORSConfiguration) { - key := bkt.Name + bkt.CORSObjectName() + key := bkt.CORSObjectName() if err := c.systemCache.PutCORS(key, cors); err != nil { c.logger.Warn(logs.CouldntCacheCors, zap.String("bucket", bkt.Name), zap.Error(err)) @@ -255,5 +255,5 @@ func (c *Cache) PutCORS(owner user.ID, bkt *data.BucketInfo, cors *data.CORSConf } func (c *Cache) DeleteCORS(bktInfo *data.BucketInfo) { - c.systemCache.Delete(bktInfo.Name + bktInfo.CORSObjectName()) + c.systemCache.Delete(bktInfo.CORSObjectName()) } diff --git a/api/layer/cors.go b/api/layer/cors.go index ce3c0c9..a9dcdc0 100644 --- a/api/layer/cors.go +++ b/api/layer/cors.go @@ -10,6 +10,8 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "go.uber.org/zap" ) @@ -37,30 +39,35 @@ func (n *Layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error { } prm := PrmObjectCreate{ - Container: p.BktInfo.CID, Payload: &buf, Filepath: p.BktInfo.CORSObjectName(), CreationTime: TimeNow(ctx), - CopiesNumber: p.CopiesNumbers, } - _, objID, _, _, err := n.objectPutAndHash(ctx, prm, p.BktInfo) + var corsBkt *data.BucketInfo + if n.corsCnrInfo == nil { + corsBkt = p.BktInfo + prm.CopiesNumber = p.CopiesNumbers + } else { + corsBkt = n.corsCnrInfo + prm.PrmAuth.PrivateKey = &n.gateKey.PrivateKey + } + + prm.Container = corsBkt.CID + + _, objID, _, _, err := n.objectPutAndHash(ctx, prm, corsBkt) if err != nil { - return fmt.Errorf("put system object: %w", err) + return fmt.Errorf("put cors object: %w", err) } - objIDToDelete, err := n.treeService.PutBucketCORS(ctx, p.BktInfo, objID) - objIDToDeleteNotFound := errorsStd.Is(err, ErrNoNodeToRemove) - if err != nil && !objIDToDeleteNotFound { + objToDelete, err := n.treeService.PutBucketCORS(ctx, p.BktInfo, newAddress(corsBkt.CID, objID)) + objToDeleteNotFound := errorsStd.Is(err, ErrNoNodeToRemove) + if err != nil && !objToDeleteNotFound { return err } - if !objIDToDeleteNotFound { - if err = n.objectDelete(ctx, p.BktInfo, objIDToDelete); err != nil { - n.reqLogger(ctx).Error(logs.CouldntDeleteCorsObject, zap.Error(err), - zap.String("cnrID", p.BktInfo.CID.EncodeToString()), - zap.String("objID", objIDToDelete.EncodeToString())) - } + if !objToDeleteNotFound { + n.deleteCORSObject(ctx, p.BktInfo, objToDelete) } n.cache.PutCORS(n.BearerOwner(ctx), p.BktInfo, cors) @@ -68,12 +75,25 @@ func (n *Layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error { return nil } +// deleteCORSObject removes object and logs in case of error. +func (n *Layer) deleteCORSObject(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) { + var prmAuth PrmAuth + corsBkt := bktInfo + if !addr.Container().Equals(bktInfo.CID) && !addr.Container().Equals(cid.ID{}) { + corsBkt = &data.BucketInfo{CID: addr.Container()} + prmAuth.PrivateKey = &n.gateKey.PrivateKey + } + + if err := n.objectDeleteWithAuth(ctx, corsBkt, addr.Object(), prmAuth); err != nil { + n.reqLogger(ctx).Error(logs.CouldntDeleteCorsObject, zap.Error(err), + zap.String("cnrID", corsBkt.CID.EncodeToString()), + zap.String("objID", addr.Object().EncodeToString())) + } +} + func (n *Layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (*data.CORSConfiguration, error) { cors, err := n.getCORS(ctx, bktInfo) if err != nil { - if errorsStd.Is(err, ErrNodeNotFound) { - return nil, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchCORSConfiguration), err.Error()) - } return nil, err } @@ -81,14 +101,22 @@ func (n *Layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (*d } func (n *Layer) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) error { - objID, err := n.treeService.DeleteBucketCORS(ctx, bktInfo) - objIDNotFound := errorsStd.Is(err, ErrNoNodeToRemove) - if err != nil && !objIDNotFound { + obj, err := n.treeService.DeleteBucketCORS(ctx, bktInfo) + objNotFound := errorsStd.Is(err, ErrNoNodeToRemove) + if err != nil && !objNotFound { return err } - if !objIDNotFound { - if err = n.objectDelete(ctx, bktInfo, objID); err != nil { - return err + + if !objNotFound { + var prmAuth PrmAuth + corsBkt := bktInfo + if !obj.Container().Equals(bktInfo.CID) && !obj.Container().Equals(cid.ID{}) { + corsBkt = &data.BucketInfo{CID: obj.Container()} + prmAuth.PrivateKey = &n.gateKey.PrivateKey + } + + if err = n.objectDeleteWithAuth(ctx, corsBkt, obj.Object(), prmAuth); err != nil { + return fmt.Errorf("delete cors object: %w", err) } } diff --git a/api/layer/layer.go b/api/layer/layer.go index 85fb7db..bf527a4 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -54,6 +54,8 @@ type ( cache *Cache treeService TreeService features FeatureSettings + gateKey *keys.PrivateKey + corsCnrInfo *data.BucketInfo } Config struct { @@ -64,6 +66,8 @@ type ( Resolver BucketResolver TreeService TreeService Features FeatureSettings + GateKey *keys.PrivateKey + CORSCnrInfo *data.BucketInfo } // AnonymousKey contains data for anonymous requests. @@ -236,6 +240,8 @@ func NewLayer(log *zap.Logger, frostFS FrostFS, config *Config) *Layer { cache: config.Cache, treeService: config.TreeService, features: config.Features, + gateKey: config.GateKey, + corsCnrInfo: config.CORSCnrInfo, } } @@ -288,6 +294,10 @@ func (n *Layer) reqLogger(ctx context.Context) *zap.Logger { } func (n *Layer) prepareAuthParameters(ctx context.Context, prm *PrmAuth, bktOwner user.ID) { + if prm.BearerToken != nil || prm.PrivateKey != nil { + return + } + if bd, err := middleware.GetBoxData(ctx); err == nil && bd.Gate.BearerToken != nil { if bd.Gate.BearerToken.Impersonate() || bktOwner.Equals(bearer.ResolveIssuer(*bd.Gate.BearerToken)) { prm.BearerToken = bd.Gate.BearerToken @@ -807,5 +817,20 @@ func (n *Layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error { } n.cache.DeleteBucket(p.BktInfo) - return n.frostFS.DeleteContainer(ctx, p.BktInfo.CID, p.SessionToken) + + corsObj, err := n.treeService.GetBucketCORS(ctx, p.BktInfo) + if err != nil { + n.reqLogger(ctx).Error(logs.GetBucketCors, zap.Error(err)) + } + + err = n.frostFS.DeleteContainer(ctx, p.BktInfo.CID, p.SessionToken) + if err != nil { + return fmt.Errorf("delete container: %w", err) + } + + if !corsObj.Container().Equals(p.BktInfo.CID) && !corsObj.Container().Equals(cid.ID{}) { + n.deleteCORSObject(ctx, p.BktInfo, corsObj) + } + + return nil } diff --git a/api/layer/object.go b/api/layer/object.go index b8625b3..f25727f 100644 --- a/api/layer/object.go +++ b/api/layer/object.go @@ -151,7 +151,17 @@ func (n *Layer) initFrostFSObjectPayloadReader(ctx context.Context, p getFrostFS // objectGet returns an object with payload in the object. func (n *Layer) objectGet(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID) (*object.Object, error) { + return n.objectGetBase(ctx, bktInfo, objID, PrmAuth{}) +} + +// objectGetWithAuth returns an object with payload in the object. Uses provided PrmAuth. +func (n *Layer) objectGetWithAuth(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, auth PrmAuth) (*object.Object, error) { + return n.objectGetBase(ctx, bktInfo, objID, auth) +} + +func (n *Layer) objectGetBase(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, auth PrmAuth) (*object.Object, error) { prm := PrmObjectRead{ + PrmAuth: auth, Container: bktInfo.CID, Object: objID, WithHeader: true, @@ -460,7 +470,17 @@ func (n *Layer) headVersion(ctx context.Context, bkt *data.BucketInfo, p *HeadOb // objectDelete puts tombstone object into frostfs. func (n *Layer) objectDelete(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID) error { + return n.objectDeleteBase(ctx, bktInfo, idObj, PrmAuth{}) +} + +// objectDeleteWithAuth puts tombstone object into frostfs. Uses provided PrmAuth. +func (n *Layer) objectDeleteWithAuth(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID, auth PrmAuth) error { + return n.objectDeleteBase(ctx, bktInfo, idObj, auth) +} + +func (n *Layer) objectDeleteBase(ctx context.Context, bktInfo *data.BucketInfo, idObj oid.ID, auth PrmAuth) error { prm := PrmObjectDelete{ + PrmAuth: auth, Container: bktInfo.CID, Object: idObj, } diff --git a/api/layer/system_object.go b/api/layer/system_object.go index aa5fa9c..ce7cbd7 100644 --- a/api/layer/system_object.go +++ b/api/layer/system_object.go @@ -12,6 +12,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" ) @@ -159,19 +160,26 @@ func (n *Layer) getCORS(ctx context.Context, bkt *data.BucketInfo) (*data.CORSCo return cors, nil } - objID, err := n.treeService.GetBucketCORS(ctx, bkt) - objIDNotFound := errorsStd.Is(err, ErrNodeNotFound) - if err != nil && !objIDNotFound { + addr, err := n.treeService.GetBucketCORS(ctx, bkt) + objNotFound := errorsStd.Is(err, ErrNodeNotFound) + if err != nil && !objNotFound { return nil, err } - if objIDNotFound { + if objNotFound { return nil, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchCORSConfiguration), err.Error()) } - obj, err := n.objectGet(ctx, bkt, objID) + var prmAuth PrmAuth + corsBkt := bkt + if !addr.Container().Equals(bkt.CID) && !addr.Container().Equals(cid.ID{}) { + corsBkt = &data.BucketInfo{CID: addr.Container()} + prmAuth.PrivateKey = &n.gateKey.PrivateKey + } + + obj, err := n.objectGetWithAuth(ctx, corsBkt, addr.Object(), prmAuth) if err != nil { - return nil, err + return nil, fmt.Errorf("get cors object: %w", err) } cors := &data.CORSConfiguration{} diff --git a/api/layer/tree_mock.go b/api/layer/tree_mock.go index 4497fd0..5b0fa49 100644 --- a/api/layer/tree_mock.go +++ b/api/layer/tree_mock.go @@ -110,36 +110,39 @@ func (t *TreeServiceMock) GetSettingsNode(_ context.Context, bktInfo *data.Bucke return settings, nil } -func (t *TreeServiceMock) GetBucketCORS(_ context.Context, bktInfo *data.BucketInfo) (oid.ID, error) { +func (t *TreeServiceMock) GetBucketCORS(_ context.Context, bktInfo *data.BucketInfo) (oid.Address, error) { systemMap, ok := t.system[bktInfo.CID.EncodeToString()] if !ok { - return oid.ID{}, nil + return oid.Address{}, nil } node, ok := systemMap["cors"] if !ok { - return oid.ID{}, nil + return oid.Address{}, nil } - return node.OID, nil + var addr oid.Address + addr.SetContainer(bktInfo.CID) + addr.SetObject(node.OID) + return addr, nil } -func (t *TreeServiceMock) PutBucketCORS(_ context.Context, bktInfo *data.BucketInfo, objID oid.ID) (oid.ID, error) { +func (t *TreeServiceMock) PutBucketCORS(_ context.Context, bktInfo *data.BucketInfo, addr oid.Address) (oid.Address, error) { systemMap, ok := t.system[bktInfo.CID.EncodeToString()] if !ok { systemMap = make(map[string]*data.BaseNodeVersion) } systemMap["cors"] = &data.BaseNodeVersion{ - OID: objID, + OID: addr.Object(), } t.system[bktInfo.CID.EncodeToString()] = systemMap - return oid.ID{}, ErrNoNodeToRemove + return oid.Address{}, ErrNoNodeToRemove } -func (t *TreeServiceMock) DeleteBucketCORS(context.Context, *data.BucketInfo) (oid.ID, error) { +func (t *TreeServiceMock) DeleteBucketCORS(context.Context, *data.BucketInfo) (oid.Address, error) { panic("implement me") } diff --git a/api/layer/tree_service.go b/api/layer/tree_service.go index 001bad4..dec1985 100644 --- a/api/layer/tree_service.go +++ b/api/layer/tree_service.go @@ -21,17 +21,17 @@ type TreeService interface { // GetBucketCORS gets an object id that corresponds to object with bucket CORS. // // If object id is not found returns ErrNodeNotFound error. - GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error) + GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error) // PutBucketCORS puts a node to a system tree and returns objectID of a previous cors config which must be deleted in FrostFS. // // If object id to remove is not found returns ErrNoNodeToRemove error. - PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID) (oid.ID, error) + PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) (oid.Address, error) // DeleteBucketCORS removes a node from a system tree and returns objID which must be deleted in FrostFS. // // If object id to remove is not found returns ErrNoNodeToRemove error. - DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error) + DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error) GetObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) (map[string]string, error) PutObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion, tagSet map[string]string) error diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index f46c39d..2fe349d 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -21,6 +21,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" @@ -37,6 +38,8 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree" + "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/netmap" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool" treepool "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree" @@ -153,13 +156,13 @@ func (a *App) init(ctx context.Context) { a.setRuntimeParameters() a.initFrostfsID(ctx) a.initPolicyStorage(ctx) - a.initAPI() + a.initAPI(ctx) a.initMetrics() a.initServers(ctx) a.initTracing(ctx) } -func (a *App) initLayer() { +func (a *App) initLayer(ctx context.Context) { a.initResolver() // prepare random key for anonymous requests @@ -171,6 +174,14 @@ func (a *App) initLayer() { var gateOwner user.ID user.IDFromKey(&gateOwner, a.key.PrivateKey.PublicKey) + var corsCnrInfo *data.BucketInfo + if a.cfg.IsSet(cfgContainersCORS) { + corsCnrInfo, err = a.fetchContainerInfo(ctx, cfgContainersCORS) + if err != nil { + a.log.Fatal(logs.CouldNotFetchCORSContainerInfo, zap.Error(err)) + } + } + layerCfg := &layer.Config{ Cache: layer.NewCache(getCacheOptions(a.cfg, a.log)), AnonKey: layer.AnonymousKey{ @@ -180,6 +191,8 @@ func (a *App) initLayer() { Resolver: a.bucketResolver, TreeService: tree.NewTree(services.NewPoolWrapper(a.treePool), a.log), Features: a.settings, + GateKey: a.key, + CORSCnrInfo: corsCnrInfo, } // prepare object layer @@ -434,8 +447,8 @@ func (s *appSettings) RetryStrategy() handler.RetryStrategy { return s.retryStrategy } -func (a *App) initAPI() { - a.initLayer() +func (a *App) initAPI(ctx context.Context) { + a.initLayer(ctx) a.initHandler() } @@ -1034,3 +1047,32 @@ func (a *App) tryReconnect(ctx context.Context, sr *http.Server) bool { return len(a.unbindServers) == 0 } + +func (a *App) fetchContainerInfo(ctx context.Context, cfgKey string) (info *data.BucketInfo, err error) { + containerString := a.cfg.GetString(cfgKey) + + var id cid.ID + if err = id.DecodeString(containerString); err != nil { + if id, err = a.bucketResolver.Resolve(ctx, containerString); err != nil { + return nil, fmt.Errorf("resolve container name %s: %w", containerString, err) + } + } + + return getContainerInfo(ctx, id, a.pool) +} + +func getContainerInfo(ctx context.Context, id cid.ID, frostFSPool *pool.Pool) (*data.BucketInfo, error) { + prm := pool.PrmContainerGet{ + ContainerID: id, + } + + res, err := frostFSPool.GetContainer(ctx, prm) + if err != nil { + return nil, err + } + + return &data.BucketInfo{ + CID: id, + HomomorphicHashDisabled: container.IsHomomorphicHashingDisabled(res), + }, nil +} diff --git a/cmd/s3-gw/app_settings.go b/cmd/s3-gw/app_settings.go index 18ad39e..3aacea4 100644 --- a/cmd/s3-gw/app_settings.go +++ b/cmd/s3-gw/app_settings.go @@ -176,6 +176,9 @@ const ( // Settings. cfgSourceIPHeader = "source_ip_header" + // Containers. + cfgContainersCORS = "containers.cors" + // Command line args. cmdHelp = "help" cmdVersion = "version" diff --git a/config/config.env b/config/config.env index 69e12f5..dd4438a 100644 --- a/config/config.env +++ b/config/config.env @@ -216,3 +216,5 @@ S3_GW_RETRY_MAX_BACKOFF=30s # Backoff strategy. `exponential` and `constant` are allowed. S3_GW_RETRY_STRATEGY=exponential +# Containers properties +S3_GW_CONTAINERS_CORS=AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj diff --git a/config/config.yaml b/config/config.yaml index 4ed6be3..aae9e0b 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -252,3 +252,7 @@ retry: max_backoff: 30s # Backoff strategy. `exponential` and `constant` are allowed. strategy: exponential + +# Containers properties +containers: + cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj diff --git a/docs/configuration.md b/docs/configuration.md index 524f91b..23ba071 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -192,6 +192,7 @@ There are some custom types used for brevity: | `proxy` | [Proxy contract configuration](#proxy-section) | | `namespaces` | [Namespaces configuration](#namespaces-section) | | `retry` | [Retry configuration](#retry-section) | +| `containers` | [Containers configuration](#containers-section) | ### General section @@ -708,3 +709,15 @@ retry: | `max_backoff` | `duration` | yes | `30s` | Max delay before next attempt. | | `strategy` | `string` | yes | `exponential` | Backoff strategy. `exponential` and `constant` are allowed. | +# `containers` section + +Section for well-known containers to store s3-related data and settings. + +```yaml +containers: + cors: AZjLTXfK4vs4ovxMic2xEJKSymMNLqdwq9JT64ASFCRj +``` + +| Parameter | Type | SIGHUP reload | Default value | Description | +|-----------|----------|---------------|---------------|--------------------------------------------------------------------------------------| +| `cors` | `string` | no | | Container name for CORS configurations. If not set, container of the bucket is used. | diff --git a/internal/logs/logs.go b/internal/logs/logs.go index e2f566b..61de4f5 100644 --- a/internal/logs/logs.go +++ b/internal/logs/logs.go @@ -149,4 +149,5 @@ const ( UnexpectedMultiNodeIDsInSubTreeMultiParts = "unexpected multi node ids in sub tree multi parts" FoundSeveralSystemNodes = "found several system nodes, latest be used" FailedToParsePartInfo = "failed to parse part info" + CouldNotFetchCORSContainerInfo = "couldn't fetch CORS container info" ) diff --git a/pkg/service/tree/tree.go b/pkg/service/tree/tree.go index 73119ca..33835aa 100644 --- a/pkg/service/tree/tree.go +++ b/pkg/service/tree/tree.go @@ -15,6 +15,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -84,6 +85,7 @@ const ( ownerKeyKV = "ownerKey" lockConfigurationKV = "LockConfiguration" oidKV = "OID" + cidKV = "CID" isCombinedKV = "IsCombined" isUnversionedKV = "IsUnversioned" @@ -443,31 +445,32 @@ func (c *Tree) PutSettingsNode(ctx context.Context, bktInfo *data.BucketInfo, se return c.service.MoveNode(ctx, bktInfo, systemTree, node.ID[ind], 0, meta) } -func (c *Tree) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error) { +func (c *Tree) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error) { node, err := c.getSystemNode(ctx, bktInfo, []string{corsFilename}) if err != nil { - return oid.ID{}, err + return oid.Address{}, err } - return node.ObjID, nil + return getCORSAddress(node) } -func (c *Tree) PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID) (oid.ID, error) { +func (c *Tree) PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) (oid.Address, error) { node, err := c.getSystemNode(ctx, bktInfo, []string{corsFilename}) isErrNotFound := errors.Is(err, layer.ErrNodeNotFound) if err != nil && !isErrNotFound { - return oid.ID{}, fmt.Errorf("couldn't get node: %w", err) + return oid.Address{}, fmt.Errorf("couldn't get node: %w", err) } meta := make(map[string]string) meta[FileNameKey] = corsFilename - meta[oidKV] = objID.EncodeToString() + meta[oidKV] = addr.Object().EncodeToString() + meta[cidKV] = addr.Container().EncodeToString() if isErrNotFound { if _, err = c.service.AddNode(ctx, bktInfo, systemTree, 0, meta); err != nil { - return oid.ID{}, err + return oid.Address{}, err } - return oid.ID{}, layer.ErrNoNodeToRemove + return oid.Address{}, layer.ErrNoNodeToRemove } ind := node.GetLatestNodeIndex() @@ -475,13 +478,18 @@ func (c *Tree) PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, objI c.reqLogger(ctx).Warn(logs.FoundSeveralBucketCorsNodes) } - return node.ObjID, c.service.MoveNode(ctx, bktInfo, systemTree, node.ID[ind], 0, meta) + prevAddr, err := getCORSAddress(node) + if err != nil { + return oid.Address{}, fmt.Errorf("couldn't get cors object addr: %w", err) + } + + return prevAddr, c.service.MoveNode(ctx, bktInfo, systemTree, node.ID[ind], 0, meta) } -func (c *Tree) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error) { +func (c *Tree) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error) { node, err := c.getSystemNode(ctx, bktInfo, []string{corsFilename}) if err != nil && !errors.Is(err, layer.ErrNodeNotFound) { - return oid.ID{}, err + return oid.Address{}, err } if node != nil { @@ -490,10 +498,30 @@ func (c *Tree) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) ( c.reqLogger(ctx).Warn(logs.FoundSeveralBucketCorsNodes) } - return node.ObjID, c.service.RemoveNode(ctx, bktInfo, systemTree, node.ID[ind]) + addr, err := getCORSAddress(node) + if err != nil { + return oid.Address{}, fmt.Errorf("couldn't get cors object addr: %w", err) + } + + return addr, c.service.RemoveNode(ctx, bktInfo, systemTree, node.ID[ind]) } - return oid.ID{}, layer.ErrNoNodeToRemove + return oid.Address{}, layer.ErrNoNodeToRemove +} + +func getCORSAddress(node *treeNode) (oid.Address, error) { + var addr oid.Address + addr.SetObject(node.ObjID) + + if cidStr, ok := node.Get(cidKV); ok { + var cnrID cid.ID + if err := cnrID.DecodeString(cidStr); err != nil { + return oid.Address{}, fmt.Errorf("couldn't decode cid: %w", err) + } + addr.SetContainer(cnrID) + } + + return addr, nil } func (c *Tree) GetObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) (map[string]string, error) {