Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1074,7 +1074,7 @@ func testStatusResponses[SRV interface {
1023,
1036,
2055,
3074,
3075,
4098,
} {
t.Run("unrecognized_"+strconv.FormatUint(uint64(code), 10), func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions client/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ func (x *PrmContainerDelete) AttachSignature(sig neofscrypto.Signature) {
// Reflects all internal errors in second return value (transport problems, response processing, etc.).
// Return errors:
// - [ErrMissingSigner]
// - [apistatus.ErrContainerLocked]
func (c *Client) ContainerDelete(ctx context.Context, id cid.ID, signer neofscrypto.Signer, prm PrmContainerDelete) error {
var err error
if c.prm.statisticCallback != nil {
Expand Down
21 changes: 21 additions & 0 deletions client/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"
"time"

apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
"github.com/nspcc-dev/neofs-sdk-go/container"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
Expand All @@ -24,6 +25,7 @@ import (
protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap"
protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs"
protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session"
protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status"
"github.com/nspcc-dev/neofs-sdk-go/session"
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
"github.com/nspcc-dev/neofs-sdk-go/stat"
Expand Down Expand Up @@ -1409,6 +1411,25 @@ func TestClient_ContainerDelete(t *testing.T) {
}
})
t.Run("statuses", func(t *testing.T) {
t.Run("locked", func(t *testing.T) {
srv := newTestDeleteContainerServer()
c := newTestContainerClient(t, srv)

st := protostatus.Status{Code: 3074}

srv.respondWithStatus(&st)

err := c.ContainerDelete(ctx, anyID, anyValidSigner, anyValidOpts)
require.ErrorIs(t, err, apistatus.ErrContainerLocked)
require.EqualError(t, err, "status: code = 3074 message = container is locked")

st.Message = "some lock context"

err = c.ContainerDelete(ctx, anyID, anyValidSigner, anyValidOpts)
require.ErrorIs(t, err, apistatus.ErrContainerLocked)
require.EqualError(t, err, "status: code = 3074 message = some lock context")
})

testStatusResponses(t, newTestDeleteContainerServer, newTestContainerClient, func(c *Client) error {
return c.ContainerDelete(ctx, anyID, anyValidSigner, anyValidOpts)
})
Expand Down
49 changes: 49 additions & 0 deletions client/status/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ var (
// ErrContainerNotFound is an instance of ContainerNotFound error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrContainerNotFound ContainerNotFound
// ErrContainerLocked is an instance of ContainerLocked error status. It's expected to be used for [errors.Is]
// and MUST NOT be changed.
ErrContainerLocked ContainerLocked
)

// ContainerNotFound describes status of the failure because of the missing container.
Expand Down Expand Up @@ -95,3 +98,49 @@ func (x EACLNotFound) protoMessage() *protostatus.Status {
}
return &protostatus.Status{Code: protostatus.EACLNotFound, Message: x.msg, Details: x.dts}
}

// ContainerLocked describes status of the failure because of the locked container.
type ContainerLocked struct {
msg string
dts []*protostatus.Status_Detail
}

// NewContainerLocked constructs ContainerLocked with given message.
func NewContainerLocked(msg string) ContainerLocked {
return ContainerLocked{msg: msg}
}

const defaultContainerLockedMsg = "container is locked"

// Error implements built-in [error] interface.
func (x ContainerLocked) Error() string {
if x.msg == "" {
x.msg = defaultContainerLockedMsg
}

return errMessageStatus(protostatus.ContainerLocked, x.msg)
}

// Is implements interface for correct checking current error type with [errors.Is].
func (x ContainerLocked) Is(target error) bool {
switch target.(type) {
default:
return errors.Is(Error, target)
case ContainerLocked, *ContainerLocked:
return true
}
}

// implements local interface defined in [ToError] func.
func (x *ContainerLocked) fromProtoMessage(st *protostatus.Status) {
x.msg = st.Message
x.dts = st.Details
}

// implements local interface defined in [FromError] func.
func (x ContainerLocked) protoMessage() *protostatus.Status {
if x.msg == "" {
x.msg = defaultContainerLockedMsg
}
return &protostatus.Status{Code: protostatus.ContainerLocked, Message: x.msg, Details: x.dts}
}
16 changes: 16 additions & 0 deletions client/status/container_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package apistatus_test

import (
"testing"

apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
"github.com/stretchr/testify/require"
)

func TestNewContainerLocked(t *testing.T) {
var e apistatus.ContainerLocked
require.EqualError(t, e, "status: code = 3074 message = container is locked")

e = apistatus.NewContainerLocked("some message")
require.EqualError(t, e, "status: code = 3074 message = some message")
}
5 changes: 4 additions & 1 deletion client/status/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ func TestErrors(t *testing.T) {
errs: []error{EACLNotFound{}, new(EACLNotFound)},
errVariable: ErrEACLNotFound,
},

{
errs: []error{ContainerLocked{}, new(ContainerLocked)},
errVariable: ErrContainerLocked,
},
{
errs: []error{SessionTokenExpired{}, new(SessionTokenExpired)},
errVariable: ErrSessionTokenExpired,
Expand Down
3 changes: 3 additions & 0 deletions client/status/v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
// Container failures:
// - [protostatus.ContainerNotFound]: *[ContainerNotFound];
// - [protostatus.EACLNotFound]: *[EACLNotFound];
// - [protostatus.ContainerLocked]: *[ContainerLocked];
//
// Session failures:
// - [protostatus.SessionTokenNotFound]: *[SessionTokenNotFound];
Expand Down Expand Up @@ -84,6 +85,8 @@ func ToError(st *protostatus.Status) error {
decoder = new(ContainerNotFound)
case protostatus.EACLNotFound:
decoder = new(EACLNotFound)
case protostatus.ContainerLocked:
decoder = new(ContainerLocked)
case protostatus.SessionTokenNotFound:
decoder = new(SessionTokenNotFound)
case protostatus.SessionTokenExpired:
Expand Down
11 changes: 11 additions & 0 deletions client/status/v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,17 @@ func TestToError(t *testing.T) {
return errors.As(err, &target)
},
},
{
new: func() error {
return new(apistatus.ContainerLocked)
},
code: 3074,
compatibleErrs: []error{apistatus.ErrContainerLocked, apistatus.ContainerLocked{}, &apistatus.ContainerLocked{}, apistatus.Error},
checkAsErr: func(err error) bool {
var target *apistatus.ContainerLocked
return errors.As(err, &target)
},
},
{
new: func() error {
return new(apistatus.SessionTokenNotFound)
Expand Down
31 changes: 30 additions & 1 deletion container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
sysAttrDisableHomohash = sysAttrPrefix + "DISABLE_HOMOMORPHIC_HASHING"
sysAttrDomainName = sysAttrPrefix + "NAME"
sysAttrDomainZone = sysAttrPrefix + "ZONE"
sysAttrLockUntil = sysAttrPrefix + "LOCK_UNTIL"
)

// Container represents descriptor of the NeoFS container. Container logically
Expand Down Expand Up @@ -168,7 +169,7 @@ func (x *Container) fromProtoMessage(m *protocontainer.Container, checkFieldPres
}

switch key {
case attributeTimestamp:
case attributeTimestamp, sysAttrLockUntil:
_, err = strconv.ParseInt(val, 10, 64)
}

Expand Down Expand Up @@ -542,3 +543,31 @@ func (x Container) Version() version.Version {
}
return version.Version{}
}

// SetLockUntil sets attribute with removal lock timestamp in Unix Timestamp
// format.
func (x *Container) SetLockUntil(until time.Time) {
x.SetAttribute(sysAttrLockUntil, strconv.FormatInt(until.Unix(), 10))
}

// GetLockUntil looks up for attribute with removal lock timestamp in Unix
// Timestamp format. If attribute is missing, GetLockUntil returns both zero.
// Otherwise, GetLockUntil parses the value. If parsing fails, GetLockUntil
// returns an error containing the value.
func (x Container) GetLockUntil() (time.Time, error) {
attr := x.Attribute(sysAttrLockUntil)
if attr == "" {
return time.Time{}, nil
}

n, err := strconv.ParseInt(attr, 10, 64)
if err != nil {
var ne *strconv.NumError
if !errors.As(err, &ne) {
panic(fmt.Sprintf("unexpected strconv.ParseInt error type %T", err))
}
return time.Time{}, fmt.Errorf("parse %q: %w", ne.Num, ne.Err)
}

return time.Unix(n, 0), nil
}
51 changes: 51 additions & 0 deletions container/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1003,3 +1003,54 @@ func TestContainer_VerifySignature(t *testing.T) {
require.False(t, validContainer.VerifySignature(sig), i)
}
}

func TestContainer_SetLockUntil(t *testing.T) {
var cnr container.Container

got, err := cnr.GetLockUntil()
require.NoError(t, err)
require.Zero(t, got)

until := time.Unix(time.Now().Unix(), 0)

cnr.SetLockUntil(until)

got, err = cnr.GetLockUntil()
require.NoError(t, err)
require.True(t, got.Equal(until))

until = until.Add(5 * time.Second)

cnr.SetLockUntil(until)

got, err = cnr.GetLockUntil()
require.NoError(t, err)
require.True(t, got.Equal(until))
}

func TestContainer_GetLockUntil(t *testing.T) {
var cnr container.Container

got, err := cnr.GetLockUntil()
require.NoError(t, err)
require.Zero(t, got)

cnr.SetAttribute("__NEOFS__LOCK_UNTIL", "foo")
_, err = cnr.GetLockUntil()
require.EqualError(t, err, `parse "foo": invalid syntax`)

for _, tc := range []struct {
s string
n int64
}{
{s: "1234567890", n: 1234567890},
{s: "0", n: 0},
{s: "-42", n: -42},
} {
cnr.SetAttribute("__NEOFS__LOCK_UNTIL", tc.s)

got, err = cnr.GetLockUntil()
require.NoError(t, err)
require.True(t, got.Equal(time.Unix(tc.n, 0)))
}
}
8 changes: 6 additions & 2 deletions proto/container/service_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions proto/container/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion proto/netmap/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions proto/object/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions proto/status/codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
QuotaExceeded = 2054
ContainerNotFound = 3072
EACLNotFound = 3073
ContainerLocked = 3074
SessionTokenNotFound = 4096
SessionTokenExpired = 4097
)
Expand Down
Loading
Loading