2021-10-04 14:30:38 +00:00
|
|
|
package layer
|
|
|
|
|
|
|
|
import (
|
2022-02-28 08:02:05 +00:00
|
|
|
"bytes"
|
2021-10-04 14:30:38 +00:00
|
|
|
"context"
|
2022-02-28 08:02:05 +00:00
|
|
|
"encoding/json"
|
2021-10-13 18:50:02 +00:00
|
|
|
"encoding/xml"
|
2022-02-28 08:02:05 +00:00
|
|
|
"fmt"
|
2022-03-04 13:07:27 +00:00
|
|
|
"strconv"
|
|
|
|
"time"
|
2021-10-04 14:30:38 +00:00
|
|
|
|
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api/data"
|
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
2022-03-04 13:07:27 +00:00
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api/layer/neofs"
|
2021-11-15 12:56:16 +00:00
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/object"
|
2022-02-08 16:54:04 +00:00
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/object/address"
|
2021-10-04 14:30:38 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
2022-02-28 13:39:04 +00:00
|
|
|
const (
|
2022-03-17 14:03:06 +00:00
|
|
|
AttributeComplianceMode = ".s3-compliance-mode"
|
|
|
|
AttributeRetainUntil = ".s3-retain-until"
|
|
|
|
AttributeExpirationEpoch = "__NEOFS__EXPIRATION_EPOCH"
|
|
|
|
AttributeSysTickEpoch = "__NEOFS__TICK_EPOCH"
|
|
|
|
AttributeSysTickTopic = "__NEOFS__TICK_TOPIC"
|
2022-02-28 13:39:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func (n *layer) PutSystemObject(ctx context.Context, p *PutSystemObjectParams) (*data.ObjectInfo, error) {
|
2021-10-13 18:50:02 +00:00
|
|
|
objInfo, err := n.putSystemObjectIntoNeoFS(ctx, p)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = n.systemCache.PutObject(systemObjectKey(p.BktInfo, p.ObjName), objInfo); err != nil {
|
|
|
|
n.log.Error("couldn't cache system object", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
return objInfo, nil
|
|
|
|
}
|
|
|
|
|
2022-02-28 13:39:04 +00:00
|
|
|
func (n *layer) HeadSystemObject(ctx context.Context, bkt *data.BucketInfo, objName string) (*data.ObjectInfo, error) {
|
2021-10-13 18:50:02 +00:00
|
|
|
if objInfo := n.systemCache.GetObject(systemObjectKey(bkt, objName)); objInfo != nil {
|
|
|
|
return objInfo, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
versions, err := n.headSystemVersions(ctx, bkt, objName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = n.systemCache.PutObject(systemObjectKey(bkt, objName), versions.getLast()); err != nil {
|
|
|
|
n.log.Error("couldn't cache system object", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
return versions.getLast(), nil
|
|
|
|
}
|
|
|
|
|
2022-02-28 13:39:04 +00:00
|
|
|
func (n *layer) DeleteSystemObject(ctx context.Context, bktInfo *data.BucketInfo, name string) error {
|
2021-11-13 20:35:50 +00:00
|
|
|
f := &findParams{
|
2022-03-01 19:02:24 +00:00
|
|
|
attr: [2]string{objectSystemAttributeName, name},
|
|
|
|
cid: bktInfo.CID,
|
2021-11-13 20:35:50 +00:00
|
|
|
}
|
|
|
|
ids, err := n.objectSearch(ctx, f)
|
2021-10-13 18:50:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-03-15 09:37:56 +00:00
|
|
|
n.systemCache.Delete(systemObjectKey(bktInfo, name))
|
2022-02-08 16:54:04 +00:00
|
|
|
for i := range ids {
|
|
|
|
if err = n.objectDelete(ctx, bktInfo.CID, &ids[i]); err != nil {
|
2021-10-13 18:50:02 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *layer) putSystemObjectIntoNeoFS(ctx context.Context, p *PutSystemObjectParams) (*data.ObjectInfo, error) {
|
2021-10-04 14:30:38 +00:00
|
|
|
versions, err := n.headSystemVersions(ctx, p.BktInfo, p.ObjName)
|
|
|
|
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-03-02 14:27:40 +00:00
|
|
|
idsToDeleteArr := updateCRDT2PSetHeaders(p.Metadata, versions, false) // false means "last write wins"
|
|
|
|
// note that updateCRDT2PSetHeaders modifies p.Metadata and must be called further processing
|
|
|
|
|
2022-03-04 13:07:27 +00:00
|
|
|
prm := neofs.PrmObjectCreate{
|
2022-03-01 19:02:24 +00:00
|
|
|
Container: *p.BktInfo.CID,
|
|
|
|
Creator: *p.BktInfo.Owner,
|
|
|
|
Attributes: make([][2]string, 2, 2+len(p.Metadata)),
|
|
|
|
Payload: p.Reader,
|
|
|
|
}
|
2021-10-04 14:30:38 +00:00
|
|
|
|
2022-03-01 19:02:24 +00:00
|
|
|
prm.Attributes[0][0], prm.Attributes[0][1] = objectSystemAttributeName, p.ObjName
|
|
|
|
prm.Attributes[1][0], prm.Attributes[1][1] = attrVersionsIgnore, "true"
|
2021-10-04 14:30:38 +00:00
|
|
|
|
|
|
|
for k, v := range p.Metadata {
|
2022-01-20 14:36:21 +00:00
|
|
|
if !IsSystemHeader(k) {
|
|
|
|
k = p.Prefix + k
|
|
|
|
}
|
2022-03-01 19:02:24 +00:00
|
|
|
|
|
|
|
if v == "" && p.Prefix == tagPrefix {
|
2021-10-04 14:30:38 +00:00
|
|
|
v = tagEmptyMark
|
|
|
|
}
|
|
|
|
|
2022-03-04 14:36:30 +00:00
|
|
|
if p.Lock != nil && len(p.Lock.Objects) > 0 {
|
|
|
|
prm.Locks = p.Lock.Objects
|
2022-03-05 08:53:01 +00:00
|
|
|
|
|
|
|
attrs, err := n.attributesFromLock(ctx, p.Lock)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
prm.Attributes = append(prm.Attributes, attrs...)
|
2022-02-28 13:39:04 +00:00
|
|
|
}
|
|
|
|
|
2022-03-01 19:02:24 +00:00
|
|
|
prm.Attributes = append(prm.Attributes, [2]string{k, v})
|
|
|
|
}
|
2021-10-04 14:30:38 +00:00
|
|
|
|
2022-04-12 06:52:44 +00:00
|
|
|
id, err := n.objectPut(ctx, prm)
|
2021-10-04 14:30:38 +00:00
|
|
|
if err != nil {
|
2022-04-12 06:52:44 +00:00
|
|
|
return nil, err
|
2021-10-04 14:30:38 +00:00
|
|
|
}
|
|
|
|
|
2022-04-25 09:57:58 +00:00
|
|
|
meta, err := n.objectHead(ctx, p.BktInfo.CID, *id)
|
2021-10-04 14:30:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, id := range idsToDeleteArr {
|
|
|
|
if err = n.objectDelete(ctx, p.BktInfo.CID, id); err != nil {
|
|
|
|
n.log.Warn("couldn't delete system object",
|
|
|
|
zap.Stringer("version id", id),
|
|
|
|
zap.String("name", p.ObjName),
|
|
|
|
zap.Error(err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-13 18:50:02 +00:00
|
|
|
return objInfoFromMeta(p.BktInfo, meta), nil
|
2021-10-04 14:30:38 +00:00
|
|
|
}
|
|
|
|
|
2021-10-13 18:50:02 +00:00
|
|
|
func (n *layer) getSystemObjectFromNeoFS(ctx context.Context, bkt *data.BucketInfo, objName string) (*object.Object, error) {
|
|
|
|
versions, err := n.headSystemVersions(ctx, bkt, objName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-10-04 14:30:38 +00:00
|
|
|
}
|
|
|
|
|
2021-10-13 18:50:02 +00:00
|
|
|
objInfo := versions.getLast()
|
|
|
|
|
2022-02-08 16:54:04 +00:00
|
|
|
var addr address.Address
|
|
|
|
|
2022-04-25 09:57:58 +00:00
|
|
|
addr.SetContainerID(*bkt.CID)
|
|
|
|
addr.SetObjectID(*objInfo.ID)
|
2022-02-08 16:54:04 +00:00
|
|
|
|
|
|
|
obj, err := n.objectGet(ctx, &addr)
|
2021-10-04 14:30:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-10-13 18:50:02 +00:00
|
|
|
if len(obj.Payload()) == 0 {
|
|
|
|
return nil, errors.GetAPIError(errors.ErrInternalError)
|
|
|
|
}
|
|
|
|
return obj, nil
|
2021-10-04 14:30:38 +00:00
|
|
|
}
|
|
|
|
|
2021-10-13 18:50:02 +00:00
|
|
|
func (n *layer) getCORS(ctx context.Context, bkt *data.BucketInfo, sysName string) (*data.CORSConfiguration, error) {
|
|
|
|
if cors := n.systemCache.GetCORS(systemObjectKey(bkt, sysName)); cors != nil {
|
|
|
|
return cors, nil
|
2021-10-04 14:30:38 +00:00
|
|
|
}
|
|
|
|
|
2021-10-13 18:50:02 +00:00
|
|
|
obj, err := n.getSystemObjectFromNeoFS(ctx, bkt, sysName)
|
2021-10-04 14:30:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-10-13 18:50:02 +00:00
|
|
|
cors := &data.CORSConfiguration{}
|
2021-10-04 14:30:38 +00:00
|
|
|
|
2021-10-13 18:50:02 +00:00
|
|
|
if err = xml.Unmarshal(obj.Payload(), &cors); err != nil {
|
2021-10-04 14:30:38 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-10-13 18:50:02 +00:00
|
|
|
if err = n.systemCache.PutCORS(systemObjectKey(bkt, sysName), cors); err != nil {
|
2022-04-25 09:57:58 +00:00
|
|
|
objID, _ := obj.ID()
|
2021-10-04 14:32:35 +00:00
|
|
|
n.log.Warn("couldn't put system meta to objects cache",
|
2022-04-25 09:57:58 +00:00
|
|
|
zap.Stringer("object id", &objID),
|
2021-10-13 18:50:02 +00:00
|
|
|
zap.Stringer("bucket id", bkt.CID),
|
2021-10-04 14:32:35 +00:00
|
|
|
zap.Error(err))
|
|
|
|
}
|
2021-10-04 14:30:38 +00:00
|
|
|
|
2021-10-13 18:50:02 +00:00
|
|
|
return cors, nil
|
2021-10-04 14:30:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (n *layer) headSystemVersions(ctx context.Context, bkt *data.BucketInfo, sysName string) (*objectVersions, error) {
|
2021-11-13 20:35:50 +00:00
|
|
|
f := &findParams{
|
2022-03-01 19:02:24 +00:00
|
|
|
attr: [2]string{objectSystemAttributeName, sysName},
|
|
|
|
cid: bkt.CID,
|
2021-11-13 20:35:50 +00:00
|
|
|
}
|
|
|
|
ids, err := n.objectSearch(ctx, f)
|
2021-10-04 14:30:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
versions := newObjectVersions(sysName)
|
2022-02-08 16:54:04 +00:00
|
|
|
for i := range ids {
|
2022-04-25 09:57:58 +00:00
|
|
|
meta, err := n.objectHead(ctx, bkt.CID, ids[i])
|
2021-10-04 14:30:38 +00:00
|
|
|
if err != nil {
|
|
|
|
n.log.Warn("couldn't head object",
|
2022-02-08 16:54:04 +00:00
|
|
|
zap.Stringer("object id", &ids[i]),
|
2021-10-04 14:30:38 +00:00
|
|
|
zap.Stringer("bucket id", bkt.CID),
|
|
|
|
zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if oi := objInfoFromMeta(bkt, meta); oi != nil {
|
|
|
|
if !isSystem(oi) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
versions.appendVersion(oi)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lastVersion := versions.getLast()
|
|
|
|
if lastVersion == nil {
|
|
|
|
return nil, errors.GetAPIError(errors.ErrNoSuchKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
return versions, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// systemObjectKey is a key to use in SystemCache.
|
|
|
|
func systemObjectKey(bktInfo *data.BucketInfo, obj string) string {
|
|
|
|
return bktInfo.Name + obj
|
|
|
|
}
|
2022-02-28 08:02:05 +00:00
|
|
|
|
|
|
|
func (n *layer) GetBucketSettings(ctx context.Context, bktInfo *data.BucketInfo) (*data.BucketSettings, error) {
|
2022-03-01 15:07:15 +00:00
|
|
|
systemKey := systemObjectKey(bktInfo, bktInfo.SettingsObjectName())
|
|
|
|
if settings := n.systemCache.GetSettings(systemKey); settings != nil {
|
2022-02-28 08:02:05 +00:00
|
|
|
return settings, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
obj, err := n.getSystemObjectFromNeoFS(ctx, bktInfo, bktInfo.SettingsObjectName())
|
|
|
|
if err != nil {
|
2022-03-31 16:25:33 +00:00
|
|
|
if errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
|
|
|
return &data.BucketSettings{}, nil
|
|
|
|
}
|
2022-02-28 08:02:05 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
settings := &data.BucketSettings{}
|
|
|
|
if err = json.Unmarshal(obj.Payload(), settings); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-03-01 15:07:15 +00:00
|
|
|
if err = n.systemCache.PutSettings(systemKey, settings); err != nil {
|
2022-04-25 09:57:58 +00:00
|
|
|
objID, _ := obj.ID()
|
2022-02-28 08:02:05 +00:00
|
|
|
n.log.Warn("couldn't put system meta to objects cache",
|
2022-04-25 09:57:58 +00:00
|
|
|
zap.Stringer("object id", &objID),
|
2022-02-28 08:02:05 +00:00
|
|
|
zap.Stringer("bucket id", bktInfo.CID),
|
|
|
|
zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
return settings, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *layer) PutBucketSettings(ctx context.Context, p *PutSettingsParams) error {
|
|
|
|
rawSettings, err := json.Marshal(p.Settings)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("couldn't marshal bucket settings")
|
|
|
|
}
|
|
|
|
|
|
|
|
s := &PutSystemObjectParams{
|
|
|
|
BktInfo: p.BktInfo,
|
|
|
|
ObjName: p.BktInfo.SettingsObjectName(),
|
|
|
|
Metadata: map[string]string{},
|
|
|
|
Reader: bytes.NewReader(rawSettings),
|
|
|
|
}
|
|
|
|
|
|
|
|
obj, err := n.putSystemObjectIntoNeoFS(ctx, s)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if obj.Size == 0 {
|
|
|
|
return errors.GetAPIError(errors.ErrInternalError)
|
|
|
|
}
|
|
|
|
|
2022-03-01 15:07:15 +00:00
|
|
|
systemKey := systemObjectKey(p.BktInfo, p.BktInfo.SettingsObjectName())
|
|
|
|
if err = n.systemCache.PutSettings(systemKey, p.Settings); err != nil {
|
2022-02-28 08:02:05 +00:00
|
|
|
n.log.Error("couldn't cache system object", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2022-02-28 13:39:04 +00:00
|
|
|
|
2022-03-05 08:53:01 +00:00
|
|
|
func (n *layer) attributesFromLock(ctx context.Context, lock *data.ObjectLock) ([][2]string, error) {
|
2022-03-04 13:07:27 +00:00
|
|
|
var result [][2]string
|
2022-03-01 15:07:15 +00:00
|
|
|
if !lock.Until.IsZero() {
|
2022-03-05 08:53:01 +00:00
|
|
|
_, exp, err := n.neoFS.TimeToEpoch(ctx, lock.Until)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2022-03-04 13:07:27 +00:00
|
|
|
}
|
2022-03-05 08:53:01 +00:00
|
|
|
|
|
|
|
attrs := [][2]string{
|
2022-03-17 14:03:06 +00:00
|
|
|
{AttributeExpirationEpoch, strconv.FormatUint(exp, 10)},
|
2022-03-05 08:53:01 +00:00
|
|
|
{AttributeRetainUntil, lock.Until.Format(time.RFC3339)},
|
|
|
|
}
|
|
|
|
|
|
|
|
result = append(result, attrs...)
|
2022-02-28 13:39:04 +00:00
|
|
|
if lock.IsCompliance {
|
2022-03-04 13:07:27 +00:00
|
|
|
attrCompliance := [2]string{
|
2022-03-05 08:53:01 +00:00
|
|
|
AttributeComplianceMode, strconv.FormatBool(true),
|
2022-03-04 13:07:27 +00:00
|
|
|
}
|
2022-02-28 13:39:04 +00:00
|
|
|
result = append(result, attrCompliance)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-05 08:53:01 +00:00
|
|
|
return result, nil
|
2022-02-28 13:39:04 +00:00
|
|
|
}
|