[#195] Set tick attribute to lock objects

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2022-03-05 11:53:01 +03:00 committed by Angira Kekteeva
parent 4c3c4b6bee
commit 4a67e4b311
8 changed files with 122 additions and 43 deletions

View file

@ -7,12 +7,11 @@ import (
"strconv"
"time"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
"github.com/nspcc-dev/neofs-s3-gw/api"
"github.com/nspcc-dev/neofs-s3-gw/api/data"
apiErrors "github.com/nspcc-dev/neofs-s3-gw/api/errors"
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
)
const (
@ -282,8 +281,6 @@ func (h *handler) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Reque
return
}
//objectv2.ReadLock()
if err = checkLockInfo(lockInfo, r.Header); err != nil {
h.logAndSendError(w, "couldn't change lock mode", reqInfo, err)
return

View file

@ -290,7 +290,7 @@ func (n *layer) Initialize(ctx context.Context, c Notificator) error {
return fmt.Errorf("already initialized")
}
if err := c.Subscribe(ctx, "lock", MsgHandlerFunc(n.handleLockTick)); err != nil {
if err := c.Subscribe(ctx, LockTopic, MsgHandlerFunc(n.handleLockTick)); err != nil {
return fmt.Errorf("couldn't initialize layer: %w", err)
}

47
api/layer/locking_test.go Normal file
View file

@ -0,0 +1,47 @@
package layer
import (
"testing"
"time"
"github.com/nspcc-dev/neofs-s3-gw/api/data"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
"github.com/stretchr/testify/require"
)
func TestObjectLockAttributes(t *testing.T) {
tc := prepareContext(t)
err := tc.layer.PutBucketSettings(tc.ctx, &PutSettingsParams{
BktInfo: tc.bktInfo,
Settings: &data.BucketSettings{VersioningEnabled: true},
})
require.NoError(t, err)
obj := tc.putObject([]byte("content obj1 v1"))
_, err = tc.layer.PutSystemObject(tc.ctx, &PutSystemObjectParams{
BktInfo: tc.bktInfo,
ObjName: obj.RetentionObject(),
Metadata: make(map[string]string),
Lock: &data.ObjectLock{
Until: time.Now(),
Objects: []oid.ID{*obj.ID},
},
})
require.NoError(t, err)
lockObj := tc.getSystemObject(obj.RetentionObject())
require.NotNil(t, lockObj)
tickTopic, tickEpoch := false, false
for _, attr := range lockObj.Attributes() {
if attr.Key() == AttributeSysTickEpoch {
tickEpoch = true
} else if attr.Key() == AttributeSysTickTopic {
tickTopic = true
}
}
require.Truef(t, tickTopic, "system header __NEOFS__TICK_TOPIC presence")
require.Truef(t, tickEpoch, "system header __NEOFS__TICK_EPOCH presence")
}

View file

@ -223,4 +223,12 @@ type NeoFS interface {
//
// Returns any error encountered which prevented the removal request to be sent.
DeleteObject(context.Context, PrmObjectDelete) error
// TimeToEpoch compute current epoch and epoch that corresponds provided time.
// Note:
// * time must be in the future
// * time will be ceil rounded to match epoch
//
// Returns any error encountered which prevented computing epochs.
TimeToEpoch(context.Context, time.Time) (uint64, uint64, error)
}

View file

@ -20,6 +20,9 @@ import (
const (
AttributeComplianceMode = ".s3-compliance-mode"
AttributeRetainUntil = ".s3-retain-until"
AttributeSysTickEpoch = "__NEOFS__TICK_EPOCH"
AttributeSysTickTopic = "__NEOFS__TICK_TOPIC"
LockTopic = "lock"
)
func (n *layer) PutSystemObject(ctx context.Context, p *PutSystemObjectParams) (*data.ObjectInfo, error) {
@ -102,7 +105,13 @@ func (n *layer) putSystemObjectIntoNeoFS(ctx context.Context, p *PutSystemObject
if p.Lock != nil && len(p.Lock.Objects) > 0 {
prm.Locks = p.Lock.Objects
prm.Attributes = append(prm.Attributes, attributesFromLock(p.Lock)...)
attrs, err := n.attributesFromLock(ctx, p.Lock)
if err != nil {
return nil, err
}
prm.Attributes = append(prm.Attributes, attrs...)
}
prm.Attributes = append(prm.Attributes, [2]string{k, v})
@ -278,22 +287,28 @@ func (n *layer) PutBucketSettings(ctx context.Context, p *PutSettingsParams) err
return nil
}
func attributesFromLock(lock *data.ObjectLock) [][2]string {
func (n *layer) attributesFromLock(ctx context.Context, lock *data.ObjectLock) ([][2]string, error) {
var result [][2]string
if !lock.Until.IsZero() {
attrRetainUntil := [2]string{
AttributeRetainUntil,
lock.Until.Format(time.RFC3339),
_, exp, err := n.neoFS.TimeToEpoch(ctx, lock.Until)
if err != nil {
return nil, err
}
result = append(result, attrRetainUntil)
attrs := [][2]string{
{AttributeSysTickEpoch, strconv.FormatUint(exp, 10)},
{AttributeSysTickTopic, LockTopic},
{AttributeRetainUntil, lock.Until.Format(time.RFC3339)},
}
result = append(result, attrs...)
if lock.IsCompliance {
attrCompliance := [2]string{
AttributeComplianceMode,
strconv.FormatBool(true),
AttributeComplianceMode, strconv.FormatBool(true),
}
result = append(result, attrCompliance)
}
}
return result
return result, nil
}

View file

@ -7,7 +7,6 @@ import (
"encoding/json"
"fmt"
"io"
"math"
"os"
"time"
@ -68,12 +67,13 @@ type NeoFS interface {
// prevented the container to be created.
CreateContainer(context.Context, PrmContainerCreate) (*cid.ID, error)
// NetworkState returns current state of the NeoFS network.
// Returns any error encountered which prevented state to be read.
// TimeToEpoch compute current epoch and epoch that corresponds provided time.
// Note:
// * time must be in the future
// * time will be ceil rounded to match epoch
//
// Returns exactly one non-nil value. Returns any error encountered which
// prevented the state to be read.
NetworkState(context.Context) (*NetworkState, error)
// Returns any error encountered which prevented computing epochs.
TimeToEpoch(context.Context, time.Time) (uint64, uint64, error)
}
// Agent contains client communicating with NeoFS and logger.
@ -213,22 +213,10 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
return err
}
netState, err := a.neoFS.NetworkState(ctx)
lifetime.Iat, lifetime.Exp, err = a.neoFS.TimeToEpoch(ctx, time.Now().Add(options.Lifetime))
if err != nil {
return err
}
lifetime.Iat = netState.Epoch
msPerEpoch := netState.EpochDuration * uint64(netState.BlockDuration)
epochLifetime := uint64(options.Lifetime.Milliseconds()) / msPerEpoch
if uint64(options.Lifetime.Milliseconds())%msPerEpoch != 0 {
epochLifetime++
}
if epochLifetime >= math.MaxUint64-lifetime.Iat {
lifetime.Exp = math.MaxUint64
} else {
lifetime.Exp = lifetime.Iat + epochLifetime
}
gatesData, err := createTokens(options, lifetime)
if err != nil {

View file

@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io"
"math"
"strconv"
"strings"
"time"
@ -44,16 +45,23 @@ func (x *NeoFS) SetConnectionPool(p *pool.Pool) {
x.pool = p
}
// NetworkState implements authmate.NeoFS interface method.
func (x *NeoFS) NetworkState(ctx context.Context) (*authmate.NetworkState, error) {
// TimeToEpoch implements authmate.NeoFS interface method.
func (x *NeoFS) TimeToEpoch(ctx context.Context, futureTime time.Time) (uint64, uint64, error) {
now := time.Now()
dur := futureTime.Sub(now)
if dur < 0 {
return 0, 0, fmt.Errorf("time '%s' must be in the future (after %s)",
futureTime.Format(time.RFC3339), now.Format(time.RFC3339))
}
conn, _, err := x.pool.Connection()
if err != nil {
return nil, fmt.Errorf("get connection from pool: %w", err)
return 0, 0, fmt.Errorf("get connection from pool: %w", err)
}
res, err := conn.NetworkInfo(ctx, client.PrmNetworkInfo{})
if err != nil {
return nil, fmt.Errorf("get network info via client: %w", err)
return 0, 0, fmt.Errorf("get network info via client: %w", err)
}
networkInfo := res.Info()
@ -74,14 +82,25 @@ func (x *NeoFS) NetworkState(ctx context.Context) (*authmate.NetworkState, error
})
if durEpoch == 0 {
return nil, errors.New("epoch duration is missing or zero")
return 0, 0, errors.New("epoch duration is missing or zero")
}
return &authmate.NetworkState{
Epoch: networkInfo.CurrentEpoch(),
BlockDuration: networkInfo.MsPerBlock(),
EpochDuration: durEpoch,
}, nil
curr := networkInfo.CurrentEpoch()
msPerEpoch := durEpoch * uint64(networkInfo.MsPerBlock())
epochLifetime := uint64(dur.Milliseconds()) / msPerEpoch
if uint64(dur.Milliseconds())%msPerEpoch != 0 {
epochLifetime++
}
var epoch uint64
if epochLifetime >= math.MaxUint64-curr {
epoch = math.MaxUint64
} else {
epoch = curr + epochLifetime
}
return curr, epoch, nil
}
// Container reads container by ID using connection pool. Returns exact one non-nil value.

View file

@ -9,6 +9,7 @@ import (
"io"
"strconv"
"strings"
"time"
objectv2 "github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-s3-gw/api/layer/neofs"
@ -240,6 +241,10 @@ func (t *TestNeoFS) DeleteObject(_ context.Context, prm neofs.PrmObjectDelete) e
return nil
}
func (t *TestNeoFS) TimeToEpoch(ctx context.Context, futureTime time.Time) (uint64, uint64, error) {
return t.currentEpoch, t.currentEpoch + uint64(futureTime.Second()), nil
}
func isMatched(attributes []*object.Attribute, filter object.SearchFilter) bool {
for _, attr := range attributes {
if attr.Key() == filter.Header() && attr.Value() == filter.Value() {