forked from TrueCloudLab/frostfs-sdk-go
Compare commits
32 commits
b464c4c3cf
...
b11bc616b4
Author | SHA1 | Date | |
---|---|---|---|
b11bc616b4 | |||
5361f0eceb | |||
05aa3becae | |||
79f387317a | |||
3ea4741231 | |||
d7872061f8 | |||
99c5c58365 | |||
4c310ae1c7 | |||
997346ef95 | |||
7f6eda566a | |||
d00892f418 | |||
b9092aeb0c | |||
1b67ab9608 | |||
99d5bf913b | |||
e50838a33d | |||
97cf56ba41 | |||
07625e3bd1 | |||
da2f0e7532 | |||
114b4c14b5 | |||
e580ee991d | |||
6821fe6fb2 | |||
6009d089fc | |||
3e455777fd | |||
1dc3b77ac7 | |||
88c6556c37 | |||
d342c0bc16 | |||
f0c599d06d | |||
7d84d104fb | |||
812126a8ff | |||
d86223ed56 | |||
76a0cfdadb | |||
46ee543899 |
54 changed files with 1360 additions and 359 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -23,7 +23,7 @@ coverage.txt
|
|||
coverage.html
|
||||
|
||||
# antlr tool jar
|
||||
antlr-*.jar
|
||||
antlr*.jar
|
||||
|
||||
# tempfiles
|
||||
.cache
|
||||
|
|
|
@ -63,5 +63,6 @@ linters:
|
|||
- funlen
|
||||
- gocognit
|
||||
- contextcheck
|
||||
- protogetter
|
||||
disable-all: true
|
||||
fast: false
|
||||
|
|
17
Makefile
17
Makefile
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
ANTLR_VERSION="4.13.0"
|
||||
ANTLR_VERSION=4.13.1
|
||||
TMP_DIR := .cache
|
||||
LINT_VERSION ?= 1.60.1
|
||||
TRUECLOUDLAB_LINT_VERSION ?= 0.0.6
|
||||
|
@ -53,7 +53,8 @@ format:
|
|||
|
||||
policy:
|
||||
@wget -q https://www.antlr.org/download/antlr-${ANTLR_VERSION}-complete.jar -O antlr4-tool.jar
|
||||
@java -Xmx500M -cp "`pwd`/antlr4-tool.jar" "org.antlr.v4.Tool" -o `pwd`/netmap/parser/ -Dlanguage=Go -no-listener -visitor `pwd`/netmap/parser/Query.g4 `pwd`/netmap/parser/QueryLexer.g4
|
||||
@java -Xmx500M -cp antlr4-tool.jar org.antlr.v4.Tool -Dlanguage=Go \
|
||||
-no-listener -visitor netmap/parser/Query.g4 netmap/parser/QueryLexer.g4
|
||||
|
||||
# Run `make %` in truecloudlab/frostfs-sdk-go container(Golang+Java)
|
||||
docker/%:
|
||||
|
@ -77,3 +78,15 @@ help:
|
|||
@echo ' Targets:'
|
||||
@echo ''
|
||||
@awk '/^#/{ comment = substr($$0,3) } comment && /^[a-zA-Z][a-zA-Z0-9_-]+ ?:/{ print " ", $$1, comment }' $(MAKEFILE_LIST) | column -t -s ':' | grep -v 'IGNORE' | sort -u
|
||||
|
||||
# Activate pre-commit hooks
|
||||
pre-commit:
|
||||
pre-commit install --hook-type pre-commit
|
||||
|
||||
# Deactivate pre-commit hooks
|
||||
unpre-commit:
|
||||
pre-commit uninstall --hook-type pre-commit
|
||||
|
||||
# Run pre-commit hooks
|
||||
pre-commit-run:
|
||||
@pre-commit run --all-files --hook-stage manual
|
||||
|
|
|
@ -65,7 +65,7 @@ func (c *Client) APEManagerListChains(ctx context.Context, prm PrmAPEManagerList
|
|||
var res ResAPEManagerListChains
|
||||
res.st, err = c.processResponse(resp)
|
||||
if err != nil || !apistatus.IsSuccessful(res.st) {
|
||||
return nil, err
|
||||
return &res, err
|
||||
}
|
||||
|
||||
for _, ch := range resp.GetBody().GetChains() {
|
||||
|
|
|
@ -11,6 +11,8 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Client represents virtual connection to the FrostFS network to communicate
|
||||
|
@ -98,13 +100,28 @@ func (c *Client) Dial(ctx context.Context, prm PrmDial) error {
|
|||
|
||||
c.setFrostFSAPIServer((*coreServer)(&c.c))
|
||||
|
||||
// TODO: (neofs-api-go#382) perform generic dial stage of the client.Client
|
||||
ctx, cancel := context.WithTimeout(ctx, prm.DialTimeout)
|
||||
defer cancel()
|
||||
_, err := rpc.Balance(&c.c, new(v2accounting.BalanceRequest),
|
||||
client.WithContext(ctx),
|
||||
)
|
||||
if err != nil {
|
||||
var ctxErr error
|
||||
|
||||
// return context errors since they signal about dial problem
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return err
|
||||
ctxErr = err
|
||||
} else if st, ok := status.FromError(err); ok && st.Code() == codes.Canceled {
|
||||
ctxErr = context.Canceled
|
||||
} else if ok && st.Code() == codes.DeadlineExceeded {
|
||||
ctxErr = context.DeadlineExceeded
|
||||
}
|
||||
if ctxErr != nil {
|
||||
if conn := c.c.Conn(); conn != nil {
|
||||
_ = conn.Close()
|
||||
}
|
||||
return ctxErr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -239,12 +239,8 @@ func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (*ResN
|
|||
|
||||
var res ResNetMapSnapshot
|
||||
res.st, err = c.processResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !apistatus.IsSuccessful(res.st) {
|
||||
return &res, nil
|
||||
if err != nil || !apistatus.IsSuccessful(res.st) {
|
||||
return &res, err
|
||||
}
|
||||
|
||||
const fieldNetMap = "network map"
|
||||
|
|
|
@ -148,12 +148,8 @@ func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObj
|
|||
|
||||
var res ResObjectDelete
|
||||
res.st, err = c.processResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !apistatus.IsSuccessful(res.st) {
|
||||
return &res, nil
|
||||
if err != nil || !apistatus.IsSuccessful(res.st) {
|
||||
return &res, err
|
||||
}
|
||||
|
||||
const fieldTombstone = "tombstone"
|
||||
|
|
|
@ -492,12 +492,8 @@ func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectH
|
|||
|
||||
var res ResObjectHead
|
||||
res.st, err = c.processResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !apistatus.IsSuccessful(res.st) {
|
||||
return &res, nil
|
||||
if err != nil || !apistatus.IsSuccessful(res.st) {
|
||||
return &res, err
|
||||
}
|
||||
|
||||
res.idObj = *prm.ObjectID
|
||||
|
|
|
@ -189,12 +189,8 @@ func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectH
|
|||
|
||||
var res ResObjectHash
|
||||
res.st, err = c.processResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !apistatus.IsSuccessful(res.st) {
|
||||
return &res, nil
|
||||
if err != nil || !apistatus.IsSuccessful(res.st) {
|
||||
return &res, err
|
||||
}
|
||||
|
||||
res.checksums = resp.GetBody().GetHashList()
|
||||
|
|
|
@ -106,7 +106,6 @@ func (c *Client) ObjectPatchInit(ctx context.Context, prm PrmObjectPatch) (Objec
|
|||
}
|
||||
objectPatcher.client = c
|
||||
objectPatcher.stream = stream
|
||||
objectPatcher.firstPatchPayload = true
|
||||
|
||||
if prm.MaxChunkLength > 0 {
|
||||
objectPatcher.maxChunkLen = prm.MaxChunkLength
|
||||
|
@ -154,8 +153,6 @@ type objectPatcher struct {
|
|||
respV2 v2object.PatchResponse
|
||||
|
||||
maxChunkLen int
|
||||
|
||||
firstPatchPayload bool
|
||||
}
|
||||
|
||||
func (x *objectPatcher) PatchAttributes(_ context.Context, newAttrs []object.Attribute, replace bool) bool {
|
||||
|
@ -171,19 +168,33 @@ func (x *objectPatcher) PatchPayload(_ context.Context, rng *object.Range, paylo
|
|||
|
||||
buf := make([]byte, x.maxChunkLen)
|
||||
|
||||
for {
|
||||
for patchIter := 0; ; patchIter++ {
|
||||
n, err := payloadReader.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
x.err = fmt.Errorf("read payload: %w", err)
|
||||
return false
|
||||
}
|
||||
if n == 0 {
|
||||
if patchIter == 0 {
|
||||
if rng.GetLength() == 0 {
|
||||
x.err = errors.New("zero-length empty payload patch can't be applied")
|
||||
return false
|
||||
}
|
||||
if !x.patch(&object.Patch{
|
||||
Address: x.addr,
|
||||
PayloadPatch: &object.PayloadPatch{
|
||||
Range: rng,
|
||||
Chunk: []byte{},
|
||||
},
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
rngPart := object.NewRange()
|
||||
if x.firstPatchPayload {
|
||||
x.firstPatchPayload = false
|
||||
if patchIter == 0 {
|
||||
rngPart.SetOffset(offset)
|
||||
rngPart.SetLength(rng.GetLength())
|
||||
} else {
|
||||
|
@ -235,12 +246,8 @@ func (x *objectPatcher) Close(_ context.Context) (*ResObjectPatch, error) {
|
|||
}
|
||||
|
||||
x.res.st, x.err = x.client.processResponse(&x.respV2)
|
||||
if x.err != nil {
|
||||
return nil, x.err
|
||||
}
|
||||
|
||||
if !apistatus.IsSuccessful(x.res.st) {
|
||||
return &x.res, nil
|
||||
if x.err != nil || !apistatus.IsSuccessful(x.res.st) {
|
||||
return &x.res, x.err
|
||||
}
|
||||
|
||||
const fieldID = "ID"
|
||||
|
|
|
@ -175,7 +175,6 @@ func TestObjectPatcher(t *testing.T) {
|
|||
addr: oidtest.Address(),
|
||||
key: pk,
|
||||
maxChunkLen: test.maxChunkLen,
|
||||
firstPatchPayload: true,
|
||||
}
|
||||
|
||||
success := patcher.PatchAttributes(context.Background(), nil, false)
|
||||
|
@ -194,6 +193,93 @@ func TestObjectPatcher(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRepeatPayloadPatch(t *testing.T) {
|
||||
t.Run("no payload patch partioning", func(t *testing.T) {
|
||||
m := &mockPatchStream{}
|
||||
|
||||
pk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
|
||||
const maxChunkLen = 20
|
||||
|
||||
patcher := objectPatcher{
|
||||
client: &Client{},
|
||||
stream: m,
|
||||
addr: oidtest.Address(),
|
||||
key: pk,
|
||||
maxChunkLen: maxChunkLen,
|
||||
}
|
||||
|
||||
for _, pp := range []struct {
|
||||
patchPayload string
|
||||
rng *object.Range
|
||||
}{
|
||||
{
|
||||
patchPayload: "xxxxxxxxxx",
|
||||
rng: newRange(1, 6),
|
||||
},
|
||||
{
|
||||
patchPayload: "yyyyyyyyyy",
|
||||
rng: newRange(5, 9),
|
||||
},
|
||||
{
|
||||
patchPayload: "zzzzzzzzzz",
|
||||
rng: newRange(10, 0),
|
||||
},
|
||||
} {
|
||||
success := patcher.PatchPayload(context.Background(), pp.rng, bytes.NewReader([]byte(pp.patchPayload)))
|
||||
require.True(t, success)
|
||||
}
|
||||
|
||||
requireRangeChunk(t, m.streamedPayloadPatches[0], 1, 6, "xxxxxxxxxx")
|
||||
requireRangeChunk(t, m.streamedPayloadPatches[1], 5, 9, "yyyyyyyyyy")
|
||||
requireRangeChunk(t, m.streamedPayloadPatches[2], 10, 0, "zzzzzzzzzz")
|
||||
})
|
||||
|
||||
t.Run("payload patch partioning", func(t *testing.T) {
|
||||
m := &mockPatchStream{}
|
||||
|
||||
pk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
|
||||
const maxChunkLen = 5
|
||||
|
||||
patcher := objectPatcher{
|
||||
client: &Client{},
|
||||
stream: m,
|
||||
addr: oidtest.Address(),
|
||||
key: pk,
|
||||
maxChunkLen: maxChunkLen,
|
||||
}
|
||||
|
||||
for _, pp := range []struct {
|
||||
patchPayload string
|
||||
rng *object.Range
|
||||
}{
|
||||
{
|
||||
patchPayload: "xxxxxxxxxx",
|
||||
rng: newRange(1, 6),
|
||||
},
|
||||
{
|
||||
patchPayload: "yyyyyyyyyy",
|
||||
rng: newRange(5, 9),
|
||||
},
|
||||
{
|
||||
patchPayload: "zzzzzzzzzz",
|
||||
rng: newRange(10, 0),
|
||||
},
|
||||
} {
|
||||
success := patcher.PatchPayload(context.Background(), pp.rng, bytes.NewReader([]byte(pp.patchPayload)))
|
||||
require.True(t, success)
|
||||
}
|
||||
|
||||
requireRangeChunk(t, m.streamedPayloadPatches[0], 1, 6, "xxxxx")
|
||||
requireRangeChunk(t, m.streamedPayloadPatches[1], 7, 0, "xxxxx")
|
||||
requireRangeChunk(t, m.streamedPayloadPatches[2], 5, 9, "yyyyy")
|
||||
requireRangeChunk(t, m.streamedPayloadPatches[3], 14, 0, "yyyyy")
|
||||
requireRangeChunk(t, m.streamedPayloadPatches[4], 10, 0, "zzzzz")
|
||||
requireRangeChunk(t, m.streamedPayloadPatches[5], 10, 0, "zzzzz")
|
||||
})
|
||||
}
|
||||
|
||||
func requireRangeChunk(t *testing.T, pp *object.PayloadPatch, offset, length int, chunk string) {
|
||||
require.NotNil(t, pp)
|
||||
require.Equal(t, uint64(offset), pp.Range.GetOffset())
|
||||
|
|
|
@ -156,12 +156,8 @@ func (x *objectWriterRaw) Close(_ context.Context) (*ResObjectPut, error) {
|
|||
}
|
||||
|
||||
x.res.st, x.err = x.client.processResponse(&x.respV2)
|
||||
if x.err != nil {
|
||||
return nil, x.err
|
||||
}
|
||||
|
||||
if !apistatus.IsSuccessful(x.res.st) {
|
||||
return &x.res, nil
|
||||
if x.err != nil || !apistatus.IsSuccessful(x.res.st) {
|
||||
return &x.res, x.err
|
||||
}
|
||||
|
||||
const fieldID = "ID"
|
||||
|
|
|
@ -167,7 +167,7 @@ func (c *Client) ObjectPutSingle(ctx context.Context, prm PrmObjectPutSingle) (*
|
|||
var res ResObjectPutSingle
|
||||
res.st, err = c.processResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return &res, err
|
||||
}
|
||||
res.epoch = resp.GetMetaHeader().GetEpoch()
|
||||
|
||||
|
|
|
@ -220,7 +220,7 @@ func (x *ObjectListReader) Close() (*ResObjectSearch, error) {
|
|||
defer x.cancelCtxStream()
|
||||
|
||||
if x.err != nil && !errors.Is(x.err, io.EOF) {
|
||||
return nil, x.err
|
||||
return &x.res, x.err
|
||||
}
|
||||
|
||||
return &x.res, nil
|
||||
|
|
|
@ -238,3 +238,61 @@ func (x *NodeUnderMaintenance) SetMessage(v string) {
|
|||
func (x NodeUnderMaintenance) Message() string {
|
||||
return x.v2.Message()
|
||||
}
|
||||
|
||||
// InvalidArgument describes failure status related to invalid argument.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
type InvalidArgument struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultInvalidArgumentMsg = "argument is invalid"
|
||||
|
||||
// Error implements the error interface.
|
||||
func (x *InvalidArgument) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultInvalidArgumentMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(status.InvalidArgument, status.GlobalizeCommonFail),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
func (x *InvalidArgument) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// Otherwise, returns message with
|
||||
// - code: INVALID_ARGUMENT;
|
||||
// - string message: written message via SetMessage or
|
||||
// "argument is invalid" as a default message;
|
||||
// - details: empty.
|
||||
func (x InvalidArgument) ToStatusV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(status.InvalidArgument, status.GlobalizeCommonFail))
|
||||
if x.v2.Message() == "" {
|
||||
x.v2.SetMessage(defaultInvalidArgumentMsg)
|
||||
}
|
||||
|
||||
return &x.v2
|
||||
}
|
||||
|
||||
// SetMessage writes invalid argument failure message.
|
||||
// Message should be used for debug purposes only.
|
||||
//
|
||||
// See also Message.
|
||||
func (x *InvalidArgument) SetMessage(v string) {
|
||||
x.v2.SetMessage(v)
|
||||
}
|
||||
|
||||
// Message returns status message. Zero status returns empty message.
|
||||
// Message should be used for debug purposes only.
|
||||
//
|
||||
// See also SetMessage.
|
||||
func (x InvalidArgument) Message() string {
|
||||
return x.v2.Message()
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ func TestNodeUnderMaintenance(t *testing.T) {
|
|||
|
||||
stV2 := st.ToStatusV2()
|
||||
|
||||
require.Empty(t, "", stV2.Message())
|
||||
require.Equal(t, "node is under maintenance", stV2.Message())
|
||||
})
|
||||
|
||||
t.Run("non-empty to V2", func(t *testing.T) {
|
||||
|
@ -128,3 +128,42 @@ func TestNodeUnderMaintenance(t *testing.T) {
|
|||
require.Equal(t, msg, stV2.Message())
|
||||
})
|
||||
}
|
||||
|
||||
func TestInvalidArgument(t *testing.T) {
|
||||
t.Run("default", func(t *testing.T) {
|
||||
var st apistatus.InvalidArgument
|
||||
|
||||
require.Empty(t, st.Message())
|
||||
})
|
||||
|
||||
t.Run("custom message", func(t *testing.T) {
|
||||
var st apistatus.InvalidArgument
|
||||
msg := "some message"
|
||||
|
||||
st.SetMessage(msg)
|
||||
|
||||
stV2 := st.ToStatusV2()
|
||||
|
||||
require.Equal(t, msg, st.Message())
|
||||
require.Equal(t, msg, stV2.Message())
|
||||
})
|
||||
|
||||
t.Run("empty to V2", func(t *testing.T) {
|
||||
var st apistatus.InvalidArgument
|
||||
|
||||
stV2 := st.ToStatusV2()
|
||||
|
||||
require.Equal(t, "argument is invalid", stV2.Message())
|
||||
})
|
||||
|
||||
t.Run("non-empty to V2", func(t *testing.T) {
|
||||
var st apistatus.InvalidArgument
|
||||
msg := "some other msg"
|
||||
|
||||
st.SetMessage(msg)
|
||||
|
||||
stV2 := st.ToStatusV2()
|
||||
|
||||
require.Equal(t, msg, stV2.Message())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -33,12 +33,29 @@ type StatusV2 interface {
|
|||
//
|
||||
// Common failures:
|
||||
// - status.Internal: *ServerInternal;
|
||||
// - status.SignatureVerificationFail: *SignatureVerification.
|
||||
// - status.WrongMagicNumber: *WrongMagicNumber;
|
||||
// - status.SignatureVerificationFail: *SignatureVerification;
|
||||
// - status.NodeUnderMaintenance: *NodeUnderMaintenance;
|
||||
// - status.InvalidArgument: *InvalidArgument.
|
||||
//
|
||||
// Object failures:
|
||||
// - object.StatusLocked: *ObjectLocked;
|
||||
// - object.StatusLockNonRegularObject: *LockNonRegularObject.
|
||||
// - object.StatusAccessDenied: *ObjectAccessDenied.
|
||||
// - object.StatusLockNonRegularObject: *LockNonRegularObject;
|
||||
// - object.StatusAccessDenied: *ObjectAccessDenied;
|
||||
// - object.StatusNotFound: *ObjectNotFound;
|
||||
// - object.StatusAlreadyRemoved: *ObjectAlreadyRemoved;
|
||||
// - object.StatusOutOfRange: *ObjectOutOfRange.
|
||||
//
|
||||
// Container failures:
|
||||
// - container.StatusNotFound: *ContainerNotFound;
|
||||
// - container.StatusEACLNotFound: *EACLNotFound.
|
||||
//
|
||||
// Session failures:
|
||||
// - session.StatusTokenNotFound: *SessionTokenNotFound;
|
||||
// - session.StatusTokenExpired: *SessionTokenExpired.
|
||||
//
|
||||
// APE Manager failures
|
||||
// - apemanager.StatusAPEManagerAccessDenied: *APEManagerAccessDenied.
|
||||
func FromStatusV2(st *status.Status) Status {
|
||||
var decoder interface {
|
||||
fromStatusV2(*status.Status)
|
||||
|
@ -61,6 +78,8 @@ func FromStatusV2(st *status.Status) Status {
|
|||
decoder = new(SignatureVerification)
|
||||
case status.NodeUnderMaintenance:
|
||||
decoder = new(NodeUnderMaintenance)
|
||||
case status.InvalidArgument:
|
||||
decoder = new(InvalidArgument)
|
||||
}
|
||||
case object.LocalizeFailStatus(&code):
|
||||
switch code {
|
||||
|
|
|
@ -61,6 +61,24 @@ func TestToStatusV2(t *testing.T) {
|
|||
}),
|
||||
codeV2: 1025,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.SignatureVerification)
|
||||
}),
|
||||
codeV2: 1026,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.NodeUnderMaintenance)
|
||||
}),
|
||||
codeV2: 1027,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.InvalidArgument)
|
||||
}),
|
||||
codeV2: 1028,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.ObjectLocked)
|
||||
|
@ -131,12 +149,6 @@ func TestToStatusV2(t *testing.T) {
|
|||
}),
|
||||
codeV2: 5120,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.NodeUnderMaintenance)
|
||||
}),
|
||||
codeV2: 1027,
|
||||
},
|
||||
} {
|
||||
var st apistatus.Status
|
||||
|
||||
|
|
|
@ -308,7 +308,7 @@ func (x *Container) SetAttribute(key, value string) {
|
|||
attrs := x.v2.GetAttributes()
|
||||
ln := len(attrs)
|
||||
|
||||
for i := 0; i < ln; i++ {
|
||||
for i := range ln {
|
||||
if attrs[i].GetKey() == key {
|
||||
attrs[i].SetValue(value)
|
||||
return
|
||||
|
@ -356,8 +356,7 @@ func (x Container) IterateUserAttributes(f func(key, val string)) {
|
|||
attrs := x.v2.GetAttributes()
|
||||
for _, attr := range attrs {
|
||||
key := attr.GetKey()
|
||||
if !strings.HasPrefix(key, container.SysAttributePrefix) &&
|
||||
!strings.HasPrefix(key, container.SysAttributePrefixNeoFS) {
|
||||
if !strings.HasPrefix(key, container.SysAttributePrefix) {
|
||||
f(key, attr.GetValue())
|
||||
}
|
||||
}
|
||||
|
@ -417,8 +416,7 @@ func DisableHomomorphicHashing(cnr *Container) {
|
|||
//
|
||||
// Zero Container has enabled hashing.
|
||||
func IsHomomorphicHashingDisabled(cnr Container) bool {
|
||||
return cnr.Attribute(container.SysAttributeHomomorphicHashing) == attributeHomoHashEnabled ||
|
||||
cnr.Attribute(container.SysAttributeHomomorphicHashingNeoFS) == attributeHomoHashEnabled
|
||||
return cnr.Attribute(container.SysAttributeHomomorphicHashing) == attributeHomoHashEnabled
|
||||
}
|
||||
|
||||
// Domain represents information about container domain registered in the NNS
|
||||
|
@ -467,9 +465,6 @@ func ReadDomain(cnr Container) (res Domain) {
|
|||
if name := cnr.Attribute(container.SysAttributeName); name != "" {
|
||||
res.SetName(name)
|
||||
res.SetZone(cnr.Attribute(container.SysAttributeZone))
|
||||
} else if name = cnr.Attribute(container.SysAttributeNameNeoFS); name != "" {
|
||||
res.SetName(name)
|
||||
res.SetZone(cnr.Attribute(container.SysAttributeZoneNeoFS))
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
@ -150,7 +150,7 @@ func assertContainsAttribute(t *testing.T, m v2container.Container, key, val str
|
|||
}
|
||||
|
||||
func TestContainer_Attribute(t *testing.T) {
|
||||
const attrKey1, attrKey2 = v2container.SysAttributePrefix + "key1", v2container.SysAttributePrefixNeoFS + "key2"
|
||||
const attrKey1, attrKey2 = v2container.SysAttributePrefix + "key1", v2container.SysAttributePrefix + "key2"
|
||||
const attrVal1, attrVal2 = "val1", "val2"
|
||||
|
||||
val := containertest.Container()
|
||||
|
|
|
@ -286,13 +286,13 @@ func equalRecords(r1, r2 Record) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(fs1); i++ {
|
||||
for i := range len(fs1) {
|
||||
if !equalFilters(fs1[i], fs2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(ts1); i++ {
|
||||
for i := range len(ts1) {
|
||||
if !equalTargets(ts1[i], ts2[i]) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -212,7 +212,7 @@ func EqualTables(t1, t2 Table) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(rs1); i++ {
|
||||
for i := range len(rs1) {
|
||||
if !equalRecords(rs1[i], rs2[i]) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ func SetTargetECDSAKeys(t *Target, pubs ...*ecdsa.PublicKey) {
|
|||
binKeys = make([][]byte, 0, ln)
|
||||
}
|
||||
|
||||
for i := 0; i < ln; i++ {
|
||||
for i := range ln {
|
||||
binKeys = append(binKeys, (*keys.PublicKey)(pubs[i]).Bytes())
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ func TargetECDSAKeys(t *Target) []*ecdsa.PublicKey {
|
|||
|
||||
pubs := make([]*ecdsa.PublicKey, ln)
|
||||
|
||||
for i := 0; i < ln; i++ {
|
||||
for i := range ln {
|
||||
p := new(keys.PublicKey)
|
||||
if p.DecodeBytes(binKeys[i]) == nil {
|
||||
pubs[i] = (*ecdsa.PublicKey)(p)
|
||||
|
@ -169,7 +169,7 @@ func equalTargets(t1, t2 Target) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(keys1); i++ {
|
||||
for i := range len(keys1) {
|
||||
if !bytes.Equal(keys1[i], keys2[i]) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ func baseBenchmarkTableBinaryComparison(b *testing.B, factor int) {
|
|||
b.StopTimer()
|
||||
b.ResetTimer()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
got, _ := t.Marshal()
|
||||
if !bytes.Equal(exp, got) {
|
||||
b.Fail()
|
||||
|
@ -38,7 +38,7 @@ func baseBenchmarkTableEqualsComparison(b *testing.B, factor int) {
|
|||
b.StopTimer()
|
||||
b.ResetTimer()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
if !eacl.EqualTables(*t, *t2) {
|
||||
b.Fail()
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ func TargetN(n int) *eacl.Target {
|
|||
x.SetRole(eacl.RoleSystem)
|
||||
keys := make([][]byte, n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
keys[i] = make([]byte, 32)
|
||||
rand.Read(keys[i])
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ func RecordN(n int) *eacl.Record {
|
|||
x.SetOperation(eacl.OperationRangeHash)
|
||||
x.SetTargets(*TargetN(n))
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
for range n {
|
||||
x.AddFilter(eacl.HeaderFromObject, eacl.MatchStringEqual, "", cidtest.ID().EncodeToString())
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ func TableN(n int) *eacl.Table {
|
|||
|
||||
x.SetCID(cidtest.ID())
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
for range n {
|
||||
x.AddRecord(RecordN(n))
|
||||
}
|
||||
|
||||
|
|
32
go.mod
32
go.mod
|
@ -3,20 +3,21 @@ module git.frostfs.info/TrueCloudLab/frostfs-sdk-go
|
|||
go 1.22
|
||||
|
||||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240902111049-c11f50efeccb
|
||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20241011114054-f0fc40e116d1
|
||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1
|
||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/klauspost/reedsolomon v1.12.1
|
||||
github.com/mr-tron/base58 v1.2.0
|
||||
github.com/multiformats/go-multiaddr v0.12.1
|
||||
github.com/nspcc-dev/neo-go v0.106.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.uber.org/zap v1.27.0
|
||||
google.golang.org/grpc v1.63.2
|
||||
google.golang.org/protobuf v1.33.0
|
||||
google.golang.org/grpc v1.66.2
|
||||
google.golang.org/protobuf v1.34.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
|
@ -28,22 +29,31 @@ require (
|
|||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
github.com/ipfs/go-cid v0.0.7 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/multiformats/go-base32 v0.0.3 // indirect
|
||||
github.com/multiformats/go-base36 v0.1.0 // indirect
|
||||
github.com/multiformats/go-multibase v0.0.3 // indirect
|
||||
github.com/multiformats/go-multihash v0.0.14 // indirect
|
||||
github.com/multiformats/go-varint v0.0.6 // indirect
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2 // indirect
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d // indirect
|
||||
github.com/nspcc-dev/rfc6979 v0.2.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 // indirect
|
||||
github.com/twmb/murmur3 v1.1.8 // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
||||
)
|
||||
|
|
86
go.sum
86
go.sum
|
@ -1,5 +1,5 @@
|
|||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240902111049-c11f50efeccb h1:p9ByDsw+H6p6LyYSx8LKFtAG/oPKQpDVMNfjPqdevTw=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240902111049-c11f50efeccb/go.mod h1:BDnEpkKMykCS8u1nLzR6SgNzCv6885RWlo5TnravQuI=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20241011114054-f0fc40e116d1 h1:ivcdxQeQDnx4srF2ezoaeVlF0FAycSAztwfIUJnUI4s=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20241011114054-f0fc40e116d1/go.mod h1:F5GS7hRb62PUy5sTYDC4ajVdeffoAfjHSSHTKUJEaYU=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e h1:kcBqZBiFIUBATUqEuvVigtkJJWQ2Gug/eYXn967o3M4=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc=
|
||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk=
|
||||
|
@ -12,14 +12,14 @@ git.frostfs.info/TrueCloudLab/tzhash v1.8.0 h1:UFMnUIk0Zh17m8rjGHJMqku2hCgaXDqjq
|
|||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0/go.mod h1:dhY+oy274hV8wGvGL4MwwMpdL3GYvaX1a8GQZQHvlF8=
|
||||
github.com/VictoriaMetrics/easyproto v0.1.4 h1:r8cNvo8o6sR4QShBXQd1bKw/VVLSQma/V2KhTBPf+Sc=
|
||||
github.com/VictoriaMetrics/easyproto v0.1.4/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
|
||||
github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
|
||||
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
|
||||
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb h1:f0BMgIjhZy4lSRHCXFbQst85f5agZAjtDMixQqBWNpc=
|
||||
|
@ -57,6 +57,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf
|
|||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY=
|
||||
github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
|
@ -69,10 +71,31 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
|
||||
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
|
||||
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
|
||||
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
|
||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI=
|
||||
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
|
||||
github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4=
|
||||
github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM=
|
||||
github.com/multiformats/go-multiaddr v0.12.1 h1:vm+BA/WZA8QZDp1pF1FWhi5CT3g1tbi5GJmqpb6wnlk=
|
||||
github.com/multiformats/go-multiaddr v0.12.1/go.mod h1:7mPkiBMmLeFipt+nNSq9pHZUeJSt8lHBgH6yhj0YQzE=
|
||||
github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk=
|
||||
github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc=
|
||||
github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc=
|
||||
github.com/multiformats/go-multihash v0.0.14 h1:QoBceQYQQtNUuf6s7wHxnE2c8bhbMqhfGzNI032se/I=
|
||||
github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc=
|
||||
github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
|
||||
github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
|
||||
github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2 h1:mD9hU3v+zJcnHAVmHnZKt3I++tvn30gBj2rP2PocZMk=
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20240301084351-0246b013f8b2/go.mod h1:U5VfmPNM88P4RORFb6KSUVBdJBDhlqggJZYGXGPxOcc=
|
||||
github.com/nspcc-dev/neo-go v0.106.2 h1:KXSJ2J5Oacc7LrX3r4jvnC8ihKqHs5NB21q4f2S3r9o=
|
||||
|
@ -102,6 +125,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
|
|||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
|
@ -121,22 +146,23 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
|
|||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -148,33 +174,33 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
|
||||
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
@ -47,7 +47,7 @@ func BenchmarkNetmap_ContainerNodes(b *testing.B) {
|
|||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_, err := nm.ContainerNodes(p, pivot)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
|
|
@ -460,6 +460,10 @@ func (x *NodeInfo) SortAttributes() {
|
|||
// SetOffline sets the state of the node to "offline". When a node updates
|
||||
// information about itself in the network map, this action is interpreted as
|
||||
// an intention to leave the network.
|
||||
//
|
||||
// See also IsOffline.
|
||||
//
|
||||
// Deprecated: use SetStatus instead.
|
||||
func (x *NodeInfo) SetOffline() {
|
||||
x.m.SetState(netmap.Offline)
|
||||
}
|
||||
|
@ -470,6 +474,8 @@ func (x *NodeInfo) SetOffline() {
|
|||
// mean online).
|
||||
//
|
||||
// See also SetOffline.
|
||||
//
|
||||
// Deprecated: use Status instead.
|
||||
func (x NodeInfo) IsOffline() bool {
|
||||
return x.m.GetState() == netmap.Offline
|
||||
}
|
||||
|
@ -479,6 +485,8 @@ func (x NodeInfo) IsOffline() bool {
|
|||
// action is interpreted as an intention to enter the network.
|
||||
//
|
||||
// See also IsOnline.
|
||||
//
|
||||
// Deprecated: use SetStatus instead.
|
||||
func (x *NodeInfo) SetOnline() {
|
||||
x.m.SetState(netmap.Online)
|
||||
}
|
||||
|
@ -489,6 +497,8 @@ func (x *NodeInfo) SetOnline() {
|
|||
// mean offline).
|
||||
//
|
||||
// See also SetOnline.
|
||||
//
|
||||
// Deprecated: use Status instead.
|
||||
func (x NodeInfo) IsOnline() bool {
|
||||
return x.m.GetState() == netmap.Online
|
||||
}
|
||||
|
@ -498,6 +508,8 @@ func (x NodeInfo) IsOnline() bool {
|
|||
// state declares temporal unavailability for a node.
|
||||
//
|
||||
// See also IsMaintenance.
|
||||
//
|
||||
// Deprecated: use SetStatus instead.
|
||||
func (x *NodeInfo) SetMaintenance() {
|
||||
x.m.SetState(netmap.Maintenance)
|
||||
}
|
||||
|
@ -507,6 +519,63 @@ func (x *NodeInfo) SetMaintenance() {
|
|||
// Zero NodeInfo has undefined state.
|
||||
//
|
||||
// See also SetMaintenance.
|
||||
//
|
||||
// Deprecated: use Status instead.
|
||||
func (x NodeInfo) IsMaintenance() bool {
|
||||
return x.m.GetState() == netmap.Maintenance
|
||||
}
|
||||
|
||||
type NodeState netmap.NodeState
|
||||
|
||||
const (
|
||||
UnspecifiedState = NodeState(netmap.UnspecifiedState)
|
||||
Online = NodeState(netmap.Online)
|
||||
Offline = NodeState(netmap.Offline)
|
||||
Maintenance = NodeState(netmap.Maintenance)
|
||||
)
|
||||
|
||||
// ToV2 converts NodeState to v2.
|
||||
func (ns NodeState) ToV2() netmap.NodeState {
|
||||
return netmap.NodeState(ns)
|
||||
}
|
||||
|
||||
// FromV2 reads NodeState to v2.
|
||||
func (ns *NodeState) FromV2(state netmap.NodeState) {
|
||||
*ns = NodeState(state)
|
||||
}
|
||||
|
||||
// Status returns the current state of the node in the network map.
|
||||
//
|
||||
// Zero NodeInfo has an undefined state, neither online nor offline.
|
||||
func (x NodeInfo) Status() NodeState {
|
||||
return NodeState(x.m.GetState())
|
||||
}
|
||||
|
||||
// SetState updates the state of the node in the network map.
|
||||
//
|
||||
// The state determines the node's current status within the network:
|
||||
// - "online": Indicates the node intends to enter the network.
|
||||
// - "offline": Indicates the node intends to leave the network.
|
||||
// - "maintenance": Indicates the node is temporarily unavailable.
|
||||
//
|
||||
// See also Status.
|
||||
func (x *NodeInfo) SetStatus(state NodeState) {
|
||||
x.m.SetState(netmap.NodeState(state))
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
//
|
||||
// String is designed to be human-readable, and its format MAY differ between
|
||||
// SDK versions.
|
||||
func (ns NodeState) String() string {
|
||||
return netmap.NodeState(ns).String()
|
||||
}
|
||||
|
||||
// IsOnline checks if the current state is "online".
|
||||
func (ns NodeState) IsOnline() bool { return ns == Online }
|
||||
|
||||
// IsOffline checks if the current state is "offline".
|
||||
func (ns NodeState) IsOffline() bool { return ns == Offline }
|
||||
|
||||
// IsMaintenance checks if the current state is "maintenance".
|
||||
func (ns NodeState) IsMaintenance() bool { return ns == Maintenance }
|
||||
|
|
|
@ -3,6 +3,7 @@ package netmap
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -23,7 +24,31 @@ func TestNodeInfo_SetAttribute(t *testing.T) {
|
|||
require.Equal(t, val, n.Attribute(key))
|
||||
}
|
||||
|
||||
func TestNodeState(t *testing.T) {
|
||||
m := map[NodeState]netmap.NodeState{
|
||||
UnspecifiedState: netmap.UnspecifiedState,
|
||||
Online: netmap.Online,
|
||||
Offline: netmap.Offline,
|
||||
Maintenance: netmap.Maintenance,
|
||||
}
|
||||
|
||||
t.Run("from sdk to v2", func(t *testing.T) {
|
||||
for stateSDK, stateV2 := range m {
|
||||
require.Equal(t, stateV2, stateSDK.ToV2())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("from v2 to sdk", func(t *testing.T) {
|
||||
for stateSDK, stateV2 := range m {
|
||||
var state NodeState
|
||||
state.FromV2(stateV2)
|
||||
require.Equal(t, stateSDK, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNodeInfo_Status(t *testing.T) {
|
||||
t.Run("deprecated getters/setters", func(t *testing.T) {
|
||||
var n NodeInfo
|
||||
|
||||
require.False(t, n.IsOnline())
|
||||
|
@ -44,6 +69,30 @@ func TestNodeInfo_Status(t *testing.T) {
|
|||
require.True(t, n.IsMaintenance())
|
||||
require.False(t, n.IsOnline())
|
||||
require.False(t, n.IsOffline())
|
||||
})
|
||||
|
||||
t.Run("brand new getters/setters", func(t *testing.T) {
|
||||
var n NodeInfo
|
||||
|
||||
require.False(t, n.Status().IsOnline())
|
||||
require.False(t, n.Status().IsOffline())
|
||||
require.False(t, n.Status().IsMaintenance())
|
||||
|
||||
n.SetStatus(Online)
|
||||
require.True(t, n.Status().IsOnline())
|
||||
require.False(t, n.Status().IsOffline())
|
||||
require.False(t, n.Status().IsMaintenance())
|
||||
|
||||
n.SetStatus(Offline)
|
||||
require.False(t, n.Status().IsOnline())
|
||||
require.True(t, n.Status().IsOffline())
|
||||
require.False(t, n.Status().IsMaintenance())
|
||||
|
||||
n.SetStatus(Maintenance)
|
||||
require.False(t, n.Status().IsOnline())
|
||||
require.False(t, n.Status().IsOffline())
|
||||
require.True(t, n.Status().IsMaintenance())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNodeInfo_ExternalAddr(t *testing.T) {
|
||||
|
|
|
@ -20,7 +20,7 @@ cbfStmt: CBF BackupFactor = NUMBER1; // container backup factor
|
|||
|
||||
selectStmt:
|
||||
SELECT Count = NUMBER1 // number of nodes to select without container backup factor *)
|
||||
(IN clause? Bucket = ident)? // bucket name
|
||||
(IN clause? Bucket = filterKey)? // bucket name
|
||||
FROM Filter = identWC // filter reference or whole netmap
|
||||
(AS Name = ident)? // optional selector name
|
||||
;
|
||||
|
|
Binary file not shown.
|
@ -1,4 +1,5 @@
|
|||
package parser
|
||||
|
||||
// ANTLR can be downloaded from https://www.antlr.org/download/antlr-4.13.0-complete.jar
|
||||
//go:generate java -Xmx500M -cp "./antlr-4.13.0-complete.jar:$CLASSPATH" org.antlr.v4.Tool -Dlanguage=Go -no-listener -visitor QueryLexer.g4 Query.g4
|
||||
// You can download ANTLR from https://www.antlr.org/download/antlr-4.13.1-complete.jar,
|
||||
// then run generate or simply run the dedicated Makefile target like this `make policy`.
|
||||
//go:generate java -Xmx500M -cp "./antlr-4.13.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool -Dlanguage=Go -no-listener -visitor QueryLexer.g4 Query.g4
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated from /repo/frostfs/sdk-go/netmap/parser/Query.g4 by ANTLR 4.13.0. DO NOT EDIT.
|
||||
// Code generated from netmap/parser/Query.g4 by ANTLR 4.13.1. DO NOT EDIT.
|
||||
|
||||
package parser // Query
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated from /repo/frostfs/sdk-go/netmap/parser/QueryLexer.g4 by ANTLR 4.13.0. DO NOT EDIT.
|
||||
// Code generated from netmap/parser/QueryLexer.g4 by ANTLR 4.13.1. DO NOT EDIT.
|
||||
|
||||
package parser
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated from /repo/frostfs/sdk-go/netmap/parser/Query.g4 by ANTLR 4.13.0. DO NOT EDIT.
|
||||
// Code generated from netmap/parser/Query.g4 by ANTLR 4.13.1. DO NOT EDIT.
|
||||
|
||||
package parser // Query
|
||||
|
||||
|
@ -93,7 +93,7 @@ func queryParserInit() {
|
|||
85, 1, 0, 0, 0, 85, 7, 1, 0, 0, 0, 86, 87, 5, 10, 0, 0, 87, 88, 5, 22,
|
||||
0, 0, 88, 9, 1, 0, 0, 0, 89, 90, 5, 11, 0, 0, 90, 96, 5, 22, 0, 0, 91,
|
||||
93, 5, 8, 0, 0, 92, 94, 3, 12, 6, 0, 93, 92, 1, 0, 0, 0, 93, 94, 1, 0,
|
||||
0, 0, 94, 95, 1, 0, 0, 0, 95, 97, 3, 28, 14, 0, 96, 91, 1, 0, 0, 0, 96,
|
||||
0, 0, 94, 95, 1, 0, 0, 0, 95, 97, 3, 20, 10, 0, 96, 91, 1, 0, 0, 0, 96,
|
||||
97, 1, 0, 0, 0, 97, 98, 1, 0, 0, 0, 98, 99, 5, 12, 0, 0, 99, 102, 3, 30,
|
||||
15, 0, 100, 101, 5, 9, 0, 0, 101, 103, 3, 28, 14, 0, 102, 100, 1, 0, 0,
|
||||
0, 102, 103, 1, 0, 0, 0, 103, 11, 1, 0, 0, 0, 104, 105, 7, 0, 0, 0, 105,
|
||||
|
@ -1364,7 +1364,7 @@ type ISelectStmtContext interface {
|
|||
SetCount(antlr.Token)
|
||||
|
||||
// GetBucket returns the Bucket rule contexts.
|
||||
GetBucket() IIdentContext
|
||||
GetBucket() IFilterKeyContext
|
||||
|
||||
// GetFilter returns the Filter rule contexts.
|
||||
GetFilter() IIdentWCContext
|
||||
|
@ -1373,7 +1373,7 @@ type ISelectStmtContext interface {
|
|||
GetName() IIdentContext
|
||||
|
||||
// SetBucket sets the Bucket rule contexts.
|
||||
SetBucket(IIdentContext)
|
||||
SetBucket(IFilterKeyContext)
|
||||
|
||||
// SetFilter sets the Filter rule contexts.
|
||||
SetFilter(IIdentWCContext)
|
||||
|
@ -1388,8 +1388,8 @@ type ISelectStmtContext interface {
|
|||
IdentWC() IIdentWCContext
|
||||
IN() antlr.TerminalNode
|
||||
AS() antlr.TerminalNode
|
||||
AllIdent() []IIdentContext
|
||||
Ident(i int) IIdentContext
|
||||
FilterKey() IFilterKeyContext
|
||||
Ident() IIdentContext
|
||||
Clause() IClauseContext
|
||||
|
||||
// IsSelectStmtContext differentiates from other interfaces.
|
||||
|
@ -1400,7 +1400,7 @@ type SelectStmtContext struct {
|
|||
antlr.BaseParserRuleContext
|
||||
parser antlr.Parser
|
||||
Count antlr.Token
|
||||
Bucket IIdentContext
|
||||
Bucket IFilterKeyContext
|
||||
Filter IIdentWCContext
|
||||
Name IIdentContext
|
||||
}
|
||||
|
@ -1436,13 +1436,13 @@ func (s *SelectStmtContext) GetCount() antlr.Token { return s.Count }
|
|||
|
||||
func (s *SelectStmtContext) SetCount(v antlr.Token) { s.Count = v }
|
||||
|
||||
func (s *SelectStmtContext) GetBucket() IIdentContext { return s.Bucket }
|
||||
func (s *SelectStmtContext) GetBucket() IFilterKeyContext { return s.Bucket }
|
||||
|
||||
func (s *SelectStmtContext) GetFilter() IIdentWCContext { return s.Filter }
|
||||
|
||||
func (s *SelectStmtContext) GetName() IIdentContext { return s.Name }
|
||||
|
||||
func (s *SelectStmtContext) SetBucket(v IIdentContext) { s.Bucket = v }
|
||||
func (s *SelectStmtContext) SetBucket(v IFilterKeyContext) { s.Bucket = v }
|
||||
|
||||
func (s *SelectStmtContext) SetFilter(v IIdentWCContext) { s.Filter = v }
|
||||
|
||||
|
@ -1484,37 +1484,28 @@ func (s *SelectStmtContext) AS() antlr.TerminalNode {
|
|||
return s.GetToken(QueryAS, 0)
|
||||
}
|
||||
|
||||
func (s *SelectStmtContext) AllIdent() []IIdentContext {
|
||||
children := s.GetChildren()
|
||||
len := 0
|
||||
for _, ctx := range children {
|
||||
if _, ok := ctx.(IIdentContext); ok {
|
||||
len++
|
||||
}
|
||||
}
|
||||
|
||||
tst := make([]IIdentContext, len)
|
||||
i := 0
|
||||
for _, ctx := range children {
|
||||
if t, ok := ctx.(IIdentContext); ok {
|
||||
tst[i] = t.(IIdentContext)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return tst
|
||||
}
|
||||
|
||||
func (s *SelectStmtContext) Ident(i int) IIdentContext {
|
||||
func (s *SelectStmtContext) FilterKey() IFilterKeyContext {
|
||||
var t antlr.RuleContext
|
||||
j := 0
|
||||
for _, ctx := range s.GetChildren() {
|
||||
if _, ok := ctx.(IIdentContext); ok {
|
||||
if j == i {
|
||||
if _, ok := ctx.(IFilterKeyContext); ok {
|
||||
t = ctx.(antlr.RuleContext)
|
||||
break
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return t.(IFilterKeyContext)
|
||||
}
|
||||
|
||||
func (s *SelectStmtContext) Ident() IIdentContext {
|
||||
var t antlr.RuleContext
|
||||
for _, ctx := range s.GetChildren() {
|
||||
if _, ok := ctx.(IIdentContext); ok {
|
||||
t = ctx.(antlr.RuleContext)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1617,7 +1608,7 @@ func (p *Query) SelectStmt() (localctx ISelectStmtContext) {
|
|||
{
|
||||
p.SetState(95)
|
||||
|
||||
var _x = p.Ident()
|
||||
var _x = p.FilterKey()
|
||||
|
||||
localctx.(*SelectStmtContext).Bucket = _x
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated from /repo/frostfs/sdk-go/netmap/parser/Query.g4 by ANTLR 4.13.0. DO NOT EDIT.
|
||||
// Code generated from netmap/parser/Query.g4 by ANTLR 4.13.1. DO NOT EDIT.
|
||||
|
||||
package parser // Query
|
||||
|
||||
|
|
|
@ -82,6 +82,13 @@ CBF 1
|
|||
SELECT 1 FROM Color
|
||||
FILTER (Color EQ Red OR Color EQ Blue OR Color EQ Yellow) AND Color NE Green AS Color`,
|
||||
},
|
||||
{
|
||||
name: "non-ascii attributes in SELECT IN",
|
||||
input: `REP 1
|
||||
CBF 1
|
||||
SELECT 1 IN SAME 'Цвет' FROM Colorful
|
||||
FILTER 'Цвет' EQ 'Красный' OR 'Цвет' EQ 'Синий' AS Colorful`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
@ -127,6 +134,11 @@ func TestDecodeSelectFilterExpr(t *testing.T) {
|
|||
SELECT 1 FROM R
|
||||
FILTER Color LIKE 'R' AS R
|
||||
`,
|
||||
`
|
||||
CBF 1
|
||||
SELECT 1 IN SAME 'Цвет' FROM Colorful
|
||||
FILTER 'Цвет' EQ 'Красный' OR 'Цвет' EQ 'Синий' AS Colorful
|
||||
`,
|
||||
} {
|
||||
_, err := DecodeSelectFilterString(s)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/binary"
|
||||
"fmt"
|
||||
mrand "math/rand"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/hrw"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -38,7 +40,7 @@ func BenchmarkHRWSort(b *testing.B) {
|
|||
b.Run("sort by index, no weight", func(b *testing.B) {
|
||||
realNodes := make([]nodes, netmapSize)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
b.StopTimer()
|
||||
copy(realNodes, vectors)
|
||||
b.StartTimer()
|
||||
|
@ -49,7 +51,7 @@ func BenchmarkHRWSort(b *testing.B) {
|
|||
b.Run("sort by value, no weight", func(b *testing.B) {
|
||||
realNodes := make([]nodes, netmapSize)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
b.StopTimer()
|
||||
copy(realNodes, vectors)
|
||||
b.StartTimer()
|
||||
|
@ -60,7 +62,7 @@ func BenchmarkHRWSort(b *testing.B) {
|
|||
b.Run("only sort by index", func(b *testing.B) {
|
||||
realNodes := make([]nodes, netmapSize)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
b.StopTimer()
|
||||
copy(realNodes, vectors)
|
||||
b.StartTimer()
|
||||
|
@ -71,7 +73,7 @@ func BenchmarkHRWSort(b *testing.B) {
|
|||
b.Run("sort by value", func(b *testing.B) {
|
||||
realNodes := make([]nodes, netmapSize)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
b.StopTimer()
|
||||
copy(realNodes, vectors)
|
||||
b.StartTimer()
|
||||
|
@ -82,7 +84,7 @@ func BenchmarkHRWSort(b *testing.B) {
|
|||
b.Run("sort by ID, then by index (deterministic)", func(b *testing.B) {
|
||||
realNodes := make([]nodes, netmapSize)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
b.StopTimer()
|
||||
copy(realNodes, vectors)
|
||||
b.StartTimer()
|
||||
|
@ -134,7 +136,7 @@ func BenchmarkPolicyHRWType(b *testing.B) {
|
|||
nm.SetNodes(nodes)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_, err := nm.ContainerNodes(p, []byte{1})
|
||||
if err != nil {
|
||||
b.Fatal()
|
||||
|
@ -195,7 +197,7 @@ func TestPlacementPolicy_DeterministicOrder(t *testing.T) {
|
|||
}
|
||||
|
||||
a, b := getIndices(t)
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
x, y := getIndices(t)
|
||||
require.Equal(t, a, x)
|
||||
require.Equal(t, b, y)
|
||||
|
@ -352,7 +354,7 @@ func TestPlacementPolicy_Unique(t *testing.T) {
|
|||
|
||||
var nodes []NodeInfo
|
||||
for i, city := range []string{"Moscow", "Berlin", "Shenzhen"} {
|
||||
for j := 0; j < 3; j++ {
|
||||
for j := range 3 {
|
||||
node := nodeInfoFromAttributes("City", city)
|
||||
node.SetPublicKey(binary.BigEndian.AppendUint16(nil, uint16(i*4+j)))
|
||||
nodes = append(nodes, node)
|
||||
|
@ -366,7 +368,7 @@ func TestPlacementPolicy_Unique(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
for i, vi := range v {
|
||||
for _, ni := range vi {
|
||||
for j := 0; j < i; j++ {
|
||||
for j := range i {
|
||||
for _, nj := range v[j] {
|
||||
require.NotEqual(t, ni.hash, nj.hash)
|
||||
}
|
||||
|
@ -455,7 +457,7 @@ func TestPlacementPolicy_MultiREP(t *testing.T) {
|
|||
for _, additional := range []int{0, 1, 2} {
|
||||
t.Run(fmt.Sprintf("unique=%t, additional=%d", unique, additional), func(t *testing.T) {
|
||||
rs := []ReplicaDescriptor{newReplica(1, "SameRU")}
|
||||
for i := 0; i < additional; i++ {
|
||||
for range additional {
|
||||
rs = append(rs, newReplica(1, ""))
|
||||
}
|
||||
|
||||
|
@ -542,6 +544,66 @@ func TestPlacementPolicy_ProcessSelectorsExceptForNodes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPlacementPolicy_NonAsciiAttributes(t *testing.T) {
|
||||
p := newPlacementPolicy(
|
||||
1,
|
||||
[]ReplicaDescriptor{
|
||||
newReplica(2, "Nodes"),
|
||||
newReplica(2, "Nodes"),
|
||||
},
|
||||
[]Selector{
|
||||
newSelector("Nodes", "Цвет", 2, "Colorful", (*Selector).SelectSame),
|
||||
},
|
||||
[]Filter{
|
||||
newFilter("Colorful", "", "", netmap.OR,
|
||||
newFilter("", "Цвет", "Красный", netmap.EQ),
|
||||
newFilter("", "Цвет", "Синий", netmap.EQ),
|
||||
),
|
||||
},
|
||||
)
|
||||
p.SetUnique(true)
|
||||
|
||||
nodes := []NodeInfo{
|
||||
nodeInfoFromAttributes("Цвет", "Красный", "Форма", "Треугольник"),
|
||||
nodeInfoFromAttributes("Цвет", "Красный", "Форма", "Круг"),
|
||||
nodeInfoFromAttributes("Цвет", "Синий", "Форма", "Треугольник"),
|
||||
nodeInfoFromAttributes("Цвет", "Синий", "Форма", "Круг"),
|
||||
nodeInfoFromAttributes("Свойство", "Мягкий", "Форма", "Треугольник"),
|
||||
nodeInfoFromAttributes("Свойство", "Теплый", "Форма", "Круг"),
|
||||
}
|
||||
for i := range nodes {
|
||||
nodes[i].SetPublicKey([]byte{byte(i)})
|
||||
}
|
||||
|
||||
redNodes := nodes[:2]
|
||||
blueNodes := nodes[2:4]
|
||||
|
||||
var nm NetMap
|
||||
nm.SetNodes(nodes)
|
||||
|
||||
pivot := make([]byte, 42)
|
||||
_, _ = rand.Read(pivot)
|
||||
|
||||
nodesPerReplica, err := nm.ContainerNodes(p, pivot)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, nodesPerReplica, 2)
|
||||
|
||||
for i := range nodesPerReplica {
|
||||
slices.SortFunc(nodesPerReplica[i], func(n1, n2 NodeInfo) int {
|
||||
pk1, pk2 := string(n1.PublicKey()), string(n2.PublicKey())
|
||||
return cmp.Compare(pk1, pk2)
|
||||
})
|
||||
}
|
||||
|
||||
redMatchFirst := reflect.DeepEqual(redNodes, nodesPerReplica[0])
|
||||
blueMatchFirst := reflect.DeepEqual(blueNodes, nodesPerReplica[0])
|
||||
|
||||
redMatchSecond := reflect.DeepEqual(redNodes, nodesPerReplica[1])
|
||||
blueMatchSecond := reflect.DeepEqual(blueNodes, nodesPerReplica[1])
|
||||
|
||||
assert.True(t, redMatchFirst && blueMatchSecond || blueMatchFirst && redMatchSecond)
|
||||
}
|
||||
|
||||
func TestSelector_SetName(t *testing.T) {
|
||||
const name = "some name"
|
||||
var s Selector
|
||||
|
|
|
@ -130,7 +130,7 @@ func BenchmarkPlacementPolicyInteropability(b *testing.B) {
|
|||
b.Run(name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
b.StartTimer()
|
||||
v, err := nm.ContainerNodes(tt.Policy, tt.Pivot)
|
||||
b.StopTimer()
|
||||
|
@ -173,7 +173,7 @@ func BenchmarkManySelects(b *testing.B) {
|
|||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_, err = nm.ContainerNodes(tt.Policy, tt.Pivot)
|
||||
if err != nil {
|
||||
b.FailNow()
|
||||
|
|
|
@ -53,7 +53,7 @@ func TestErasureCodeReconstruct(t *testing.T) {
|
|||
})
|
||||
t.Run("from parity", func(t *testing.T) {
|
||||
parts := cloneSlice(parts)
|
||||
for i := 0; i < parityCount; i++ {
|
||||
for i := range parityCount {
|
||||
parts[i] = nil
|
||||
}
|
||||
reconstructed, err := c.ReconstructHeader(parts)
|
||||
|
@ -138,7 +138,7 @@ func TestErasureCodeReconstruct(t *testing.T) {
|
|||
})
|
||||
t.Run("from parity", func(t *testing.T) {
|
||||
parts := cloneSlice(parts)
|
||||
for i := 0; i < parityCount; i++ {
|
||||
for i := range parityCount {
|
||||
parts[i] = nil
|
||||
}
|
||||
reconstructed, err := c.Reconstruct(parts)
|
||||
|
@ -180,7 +180,7 @@ func TestErasureCodeReconstruct(t *testing.T) {
|
|||
t.Run("from parity", func(t *testing.T) {
|
||||
oldParts := parts
|
||||
parts := cloneSlice(parts)
|
||||
for i := 0; i < parityCount; i++ {
|
||||
for i := range parityCount {
|
||||
parts[i] = nil
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ func TestID_Equal(t *testing.T) {
|
|||
|
||||
func TestID_Parse(t *testing.T) {
|
||||
t.Run("should parse successful", func(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
cs := randSHA256Checksum(t)
|
||||
str := base58.Encode(cs[:])
|
||||
|
@ -78,7 +78,7 @@ func TestID_Parse(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should failure on parse", func(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
j := i
|
||||
t.Run(strconv.Itoa(j), func(t *testing.T) {
|
||||
cs := []byte{1, 2, 3, 4, 5, byte(j)}
|
||||
|
@ -98,7 +98,7 @@ func TestID_String(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("should be equal", func(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
cs := randSHA256Checksum(t)
|
||||
str := base58.Encode(cs[:])
|
||||
|
|
|
@ -3,7 +3,10 @@ package object
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
|
@ -312,6 +315,23 @@ func (o *Object) Attributes() []Attribute {
|
|||
return res
|
||||
}
|
||||
|
||||
// UserAttributes returns object user attributes.
|
||||
func (o *Object) UserAttributes() []Attribute {
|
||||
attrs := (*object.Object)(o).
|
||||
GetHeader().
|
||||
GetAttributes()
|
||||
|
||||
res := make([]Attribute, 0, len(attrs))
|
||||
|
||||
for _, attr := range attrs {
|
||||
if !strings.HasPrefix(attr.GetKey(), container.SysAttributePrefix) {
|
||||
res = append(res, *NewAttributeFromV2(&attr))
|
||||
}
|
||||
}
|
||||
|
||||
return slices.Clip(res)
|
||||
}
|
||||
|
||||
// SetAttributes sets object attributes.
|
||||
func (o *Object) SetAttributes(v ...Attribute) {
|
||||
attrs := make([]object.Attribute, len(v))
|
||||
|
|
|
@ -3,8 +3,10 @@ package object_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
objecttest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/test"
|
||||
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -24,3 +26,26 @@ func TestInitCreation(t *testing.T) {
|
|||
require.Equal(t, cnr, cID)
|
||||
require.Equal(t, own, o.OwnerID())
|
||||
}
|
||||
|
||||
func Test_Attributes(t *testing.T) {
|
||||
obj := objecttest.Object()
|
||||
|
||||
t.Run("get user attributes", func(t *testing.T) {
|
||||
// See how we create a test object. It's created with two attributes.
|
||||
require.Len(t, obj.UserAttributes(), 2)
|
||||
})
|
||||
|
||||
userAttrs := obj.UserAttributes()
|
||||
|
||||
sysAttr := *object.NewAttribute()
|
||||
sysAttr.SetKey(v2container.SysAttributePrefix + "key")
|
||||
sysAttr.SetValue("value")
|
||||
|
||||
attr := append(userAttrs, sysAttr)
|
||||
obj.SetAttributes(attr...)
|
||||
|
||||
t.Run("get attributes", func(t *testing.T) {
|
||||
require.ElementsMatch(t, obj.UserAttributes(), userAttrs)
|
||||
require.ElementsMatch(t, obj.Attributes(), attr)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ func generateIDList(sz int) []oid.ID {
|
|||
res := make([]oid.ID, sz)
|
||||
cs := [sha256.Size]byte{}
|
||||
|
||||
for i := 0; i < sz; i++ {
|
||||
for i := range sz {
|
||||
var oID oid.ID
|
||||
|
||||
res[i] = oID
|
||||
|
|
|
@ -72,7 +72,7 @@ func TestTransformer(t *testing.T) {
|
|||
require.Equal(t, ids.ParentID, &parID)
|
||||
|
||||
children := tt.objects[i].Children()
|
||||
for j := 0; j < i; j++ {
|
||||
for j := range i {
|
||||
id, ok := tt.objects[j].ID()
|
||||
require.True(t, ok)
|
||||
require.Equal(t, id, children[j])
|
||||
|
@ -152,7 +152,7 @@ func benchmarkTransformer(b *testing.B, header *objectSDK.Object, payloadSize, s
|
|||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
f, _ := newPayloadSizeLimiter(maxSize, uint64(sizeHint), func() ObjectWriter { return benchTarget{} })
|
||||
if err := f.WriteHeader(ctx, header); err != nil {
|
||||
b.Fatalf("write header: %v", err)
|
||||
|
|
|
@ -26,11 +26,15 @@ type mockClient struct {
|
|||
|
||||
errorOnDial bool
|
||||
errorOnCreateSession bool
|
||||
errorOnEndpointInfo bool
|
||||
errorOnEndpointInfo error
|
||||
resOnEndpointInfo netmap.NodeInfo
|
||||
healthcheckFn func()
|
||||
errorOnNetworkInfo bool
|
||||
stOnGetObject apistatus.Status
|
||||
}
|
||||
|
||||
var _ client = (*mockClient)(nil)
|
||||
|
||||
func newMockClient(addr string, key ecdsa.PrivateKey) *mockClient {
|
||||
return &mockClient{
|
||||
key: key,
|
||||
|
@ -38,6 +42,16 @@ func newMockClient(addr string, key ecdsa.PrivateKey) *mockClient {
|
|||
}
|
||||
}
|
||||
|
||||
func newMockClientHealthy(addr string, key ecdsa.PrivateKey, healthy bool) *mockClient {
|
||||
m := newMockClient(addr, key)
|
||||
if healthy {
|
||||
m.setHealthy()
|
||||
} else {
|
||||
m.setUnhealthy()
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *mockClient) setThreshold(threshold uint32) {
|
||||
m.errorThreshold = threshold
|
||||
}
|
||||
|
@ -47,11 +61,11 @@ func (m *mockClient) errOnCreateSession() {
|
|||
}
|
||||
|
||||
func (m *mockClient) errOnEndpointInfo() {
|
||||
m.errorOnEndpointInfo = true
|
||||
m.errorOnEndpointInfo = errors.New("error")
|
||||
}
|
||||
|
||||
func (m *mockClient) errOnNetworkInfo() {
|
||||
m.errorOnEndpointInfo = true
|
||||
m.errorOnEndpointInfo = errors.New("error")
|
||||
}
|
||||
|
||||
func (m *mockClient) errOnDial() {
|
||||
|
@ -94,27 +108,32 @@ func (m *mockClient) containerDelete(context.Context, PrmContainerDelete) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *mockClient) apeManagerAddChain(ctx context.Context, prm PrmAddAPEChain) error {
|
||||
func (m *mockClient) apeManagerAddChain(context.Context, PrmAddAPEChain) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockClient) apeManagerRemoveChain(ctx context.Context, prm PrmRemoveAPEChain) error {
|
||||
func (m *mockClient) apeManagerRemoveChain(context.Context, PrmRemoveAPEChain) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockClient) apeManagerListChains(ctx context.Context, prm PrmListAPEChains) ([]ape.Chain, error) {
|
||||
func (m *mockClient) apeManagerListChains(context.Context, PrmListAPEChains) ([]ape.Chain, error) {
|
||||
return []ape.Chain{}, nil
|
||||
}
|
||||
|
||||
func (m *mockClient) endpointInfo(ctx context.Context, _ prmEndpointInfo) (netmap.NodeInfo, error) {
|
||||
var ni netmap.NodeInfo
|
||||
|
||||
if m.errorOnEndpointInfo {
|
||||
return ni, m.handleError(ctx, nil, errors.New("error"))
|
||||
if m.errorOnEndpointInfo != nil {
|
||||
return netmap.NodeInfo{}, m.handleError(ctx, nil, m.errorOnEndpointInfo)
|
||||
}
|
||||
|
||||
ni.SetNetworkEndpoints(m.addr)
|
||||
return ni, nil
|
||||
m.resOnEndpointInfo.SetNetworkEndpoints(m.addr)
|
||||
return m.resOnEndpointInfo, nil
|
||||
}
|
||||
|
||||
func (m *mockClient) healthcheck(ctx context.Context) (netmap.NodeInfo, error) {
|
||||
if m.healthcheckFn != nil {
|
||||
m.healthcheckFn()
|
||||
}
|
||||
return m.endpointInfo(ctx, prmEndpointInfo{})
|
||||
}
|
||||
|
||||
func (m *mockClient) networkInfo(ctx context.Context, _ prmNetworkInfo) (netmap.NetworkInfo, error) {
|
||||
|
@ -190,16 +209,12 @@ func (m *mockClient) dial(context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *mockClient) restartIfUnhealthy(ctx context.Context) (changed bool, err error) {
|
||||
_, err = m.endpointInfo(ctx, prmEndpointInfo{})
|
||||
healthy := err == nil
|
||||
changed = healthy != m.isHealthy()
|
||||
if healthy {
|
||||
m.setHealthy()
|
||||
} else {
|
||||
m.setUnhealthy()
|
||||
func (m *mockClient) restart(context.Context) error {
|
||||
if m.errorOnDial {
|
||||
return errors.New("restart dial error")
|
||||
}
|
||||
return
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockClient) close() error {
|
||||
|
|
169
pool/pool.go
169
pool/pool.go
|
@ -57,6 +57,8 @@ type client interface {
|
|||
apeManagerListChains(context.Context, PrmListAPEChains) ([]ape.Chain, error)
|
||||
// see clientWrapper.endpointInfo.
|
||||
endpointInfo(context.Context, prmEndpointInfo) (netmap.NodeInfo, error)
|
||||
// see clientWrapper.healthcheck.
|
||||
healthcheck(ctx context.Context) (netmap.NodeInfo, error)
|
||||
// see clientWrapper.networkInfo.
|
||||
networkInfo(context.Context, prmNetworkInfo) (netmap.NetworkInfo, error)
|
||||
// see clientWrapper.netMapSnapshot
|
||||
|
@ -82,8 +84,8 @@ type client interface {
|
|||
|
||||
// see clientWrapper.dial.
|
||||
dial(ctx context.Context) error
|
||||
// see clientWrapper.restartIfUnhealthy.
|
||||
restartIfUnhealthy(ctx context.Context) (bool, error)
|
||||
// see clientWrapper.restart.
|
||||
restart(ctx context.Context) error
|
||||
// see clientWrapper.close.
|
||||
close() error
|
||||
}
|
||||
|
@ -92,10 +94,10 @@ type client interface {
|
|||
type clientStatus interface {
|
||||
// isHealthy checks if the connection can handle requests.
|
||||
isHealthy() bool
|
||||
// isDialed checks if the connection was created.
|
||||
isDialed() bool
|
||||
// setUnhealthy marks client as unhealthy.
|
||||
setUnhealthy()
|
||||
// setHealthy marks client as healthy.
|
||||
setHealthy()
|
||||
// address return address of endpoint.
|
||||
address() string
|
||||
// currentErrorRate returns current errors rate.
|
||||
|
@ -126,15 +128,10 @@ type clientStatusMonitor struct {
|
|||
|
||||
// values for healthy status of clientStatusMonitor.
|
||||
const (
|
||||
// statusUnhealthyOnDial is set when dialing to the endpoint is failed,
|
||||
// so there is no connection to the endpoint, and pool should not close it
|
||||
// before re-establishing connection once again.
|
||||
statusUnhealthyOnDial = iota
|
||||
|
||||
// statusUnhealthyOnRequest is set when communication after dialing to the
|
||||
// endpoint is failed due to immediate or accumulated errors, connection is
|
||||
// available and pool should close it before re-establishing connection once again.
|
||||
statusUnhealthyOnRequest
|
||||
statusUnhealthyOnRequest = iota
|
||||
|
||||
// statusHealthy is set when connection is ready to be used by the pool.
|
||||
statusHealthy
|
||||
|
@ -233,6 +230,7 @@ func newClientStatusMonitor(logger *zap.Logger, addr string, errorThreshold uint
|
|||
type clientWrapper struct {
|
||||
clientMutex sync.RWMutex
|
||||
client *sdkClient.Client
|
||||
dialed bool
|
||||
prm wrapperPrm
|
||||
|
||||
clientStatusMonitor
|
||||
|
@ -342,30 +340,17 @@ func (c *clientWrapper) dial(ctx context.Context) error {
|
|||
GRPCDialOptions: c.prm.dialOptions,
|
||||
}
|
||||
|
||||
if err = cl.Dial(ctx, prmDial); err != nil {
|
||||
c.setUnhealthyOnDial()
|
||||
err = cl.Dial(ctx, prmDial)
|
||||
c.setDialed(err == nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// restartIfUnhealthy checks healthy status of client and recreate it if status is unhealthy.
|
||||
// Indicating if status was changed by this function call and returns error that caused unhealthy status.
|
||||
func (c *clientWrapper) restartIfUnhealthy(ctx context.Context) (changed bool, err error) {
|
||||
var wasHealthy bool
|
||||
if _, err = c.endpointInfo(ctx, prmEndpointInfo{}); err == nil {
|
||||
return false, nil
|
||||
} else if !errors.Is(err, errPoolClientUnhealthy) {
|
||||
wasHealthy = true
|
||||
}
|
||||
|
||||
// if connection is dialed before, to avoid routine / connection leak,
|
||||
// pool has to close it and then initialize once again.
|
||||
if c.isDialed() {
|
||||
c.scheduleGracefulClose()
|
||||
}
|
||||
|
||||
// restart recreates and redial inner sdk client.
|
||||
func (c *clientWrapper) restart(ctx context.Context) error {
|
||||
var cl sdkClient.Client
|
||||
prmInit := sdkClient.PrmInit{
|
||||
Key: c.prm.key,
|
||||
|
@ -381,22 +366,35 @@ func (c *clientWrapper) restartIfUnhealthy(ctx context.Context) (changed bool, e
|
|||
GRPCDialOptions: c.prm.dialOptions,
|
||||
}
|
||||
|
||||
if err = cl.Dial(ctx, prmDial); err != nil {
|
||||
c.setUnhealthyOnDial()
|
||||
return wasHealthy, err
|
||||
// if connection is dialed before, to avoid routine / connection leak,
|
||||
// pool has to close it and then initialize once again.
|
||||
if c.isDialed() {
|
||||
c.scheduleGracefulClose()
|
||||
}
|
||||
|
||||
err := cl.Dial(ctx, prmDial)
|
||||
c.setDialed(err == nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.clientMutex.Lock()
|
||||
c.client = &cl
|
||||
c.clientMutex.Unlock()
|
||||
|
||||
if _, err = cl.EndpointInfo(ctx, sdkClient.PrmEndpointInfo{}); err != nil {
|
||||
c.setUnhealthy()
|
||||
return wasHealthy, err
|
||||
return nil
|
||||
}
|
||||
|
||||
c.setHealthy()
|
||||
return !wasHealthy, nil
|
||||
func (c *clientWrapper) isDialed() bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.dialed
|
||||
}
|
||||
|
||||
func (c *clientWrapper) setDialed(dialed bool) {
|
||||
c.mu.Lock()
|
||||
c.dialed = dialed
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *clientWrapper) getClient() (*sdkClient.Client, error) {
|
||||
|
@ -654,6 +652,15 @@ func (c *clientWrapper) endpointInfo(ctx context.Context, _ prmEndpointInfo) (ne
|
|||
return netmap.NodeInfo{}, err
|
||||
}
|
||||
|
||||
return c.endpointInfoRaw(ctx, cl)
|
||||
}
|
||||
|
||||
func (c *clientWrapper) healthcheck(ctx context.Context) (netmap.NodeInfo, error) {
|
||||
cl := c.getClientRaw()
|
||||
return c.endpointInfoRaw(ctx, cl)
|
||||
}
|
||||
|
||||
func (c *clientWrapper) endpointInfoRaw(ctx context.Context, cl *sdkClient.Client) (netmap.NodeInfo, error) {
|
||||
start := time.Now()
|
||||
res, err := cl.EndpointInfo(ctx, sdkClient.PrmEndpointInfo{})
|
||||
c.incRequests(time.Since(start), methodEndpointInfo)
|
||||
|
@ -1085,7 +1092,7 @@ func (c *clientWrapper) objectSearch(ctx context.Context, prm PrmObjectSearch) (
|
|||
return ResObjectSearch{}, fmt.Errorf("init object searching on client: %w", err)
|
||||
}
|
||||
|
||||
return ResObjectSearch{r: res}, nil
|
||||
return ResObjectSearch{r: res, handleError: c.handleError}, nil
|
||||
}
|
||||
|
||||
// sessionCreate invokes sdkClient.SessionCreate parse response status to error and return result as is.
|
||||
|
@ -1121,10 +1128,6 @@ func (c *clientStatusMonitor) isHealthy() bool {
|
|||
return c.healthy.Load() == statusHealthy
|
||||
}
|
||||
|
||||
func (c *clientStatusMonitor) isDialed() bool {
|
||||
return c.healthy.Load() != statusUnhealthyOnDial
|
||||
}
|
||||
|
||||
func (c *clientStatusMonitor) setHealthy() {
|
||||
c.healthy.Store(statusHealthy)
|
||||
}
|
||||
|
@ -1133,10 +1136,6 @@ func (c *clientStatusMonitor) setUnhealthy() {
|
|||
c.healthy.Store(statusUnhealthyOnRequest)
|
||||
}
|
||||
|
||||
func (c *clientStatusMonitor) setUnhealthyOnDial() {
|
||||
c.healthy.Store(statusUnhealthyOnDial)
|
||||
}
|
||||
|
||||
func (c *clientStatusMonitor) address() string {
|
||||
return c.addr
|
||||
}
|
||||
|
@ -1159,6 +1158,16 @@ func (c *clientStatusMonitor) incErrorRate() {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *clientStatusMonitor) incErrorRateToUnhealthy(err error) {
|
||||
c.mu.Lock()
|
||||
c.currentErrorCount = 0
|
||||
c.overallErrorCount++
|
||||
c.setUnhealthy()
|
||||
c.mu.Unlock()
|
||||
|
||||
c.log(zapcore.WarnLevel, "explicitly mark node unhealthy", zap.String("address", c.addr), zap.Error(err))
|
||||
}
|
||||
|
||||
func (c *clientStatusMonitor) log(level zapcore.Level, msg string, fields ...zap.Field) {
|
||||
if c.logger == nil {
|
||||
return
|
||||
|
@ -1201,6 +1210,9 @@ func (c *clientWrapper) incRequests(elapsed time.Duration, method MethodIndex) {
|
|||
}
|
||||
|
||||
func (c *clientWrapper) close() error {
|
||||
if !c.isDialed() {
|
||||
return nil
|
||||
}
|
||||
if cl := c.getClientRaw(); cl != nil {
|
||||
return cl.Close()
|
||||
}
|
||||
|
@ -1225,9 +1237,10 @@ func (c *clientStatusMonitor) handleError(ctx context.Context, st apistatus.Stat
|
|||
switch stErr.(type) {
|
||||
case *apistatus.ServerInternal,
|
||||
*apistatus.WrongMagicNumber,
|
||||
*apistatus.SignatureVerification,
|
||||
*apistatus.NodeUnderMaintenance:
|
||||
*apistatus.SignatureVerification:
|
||||
c.incErrorRate()
|
||||
case *apistatus.NodeUnderMaintenance:
|
||||
c.incErrorRateToUnhealthy(stErr)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
|
@ -1239,8 +1252,12 @@ func (c *clientStatusMonitor) handleError(ctx context.Context, st apistatus.Stat
|
|||
|
||||
if err != nil {
|
||||
if needCountError(ctx, err) {
|
||||
if sdkClient.IsErrNodeUnderMaintenance(err) {
|
||||
c.incErrorRateToUnhealthy(err)
|
||||
} else {
|
||||
c.incErrorRate()
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -1261,7 +1278,7 @@ func needCountError(ctx context.Context, err error) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if errors.Is(ctx.Err(), context.Canceled) {
|
||||
if ctx != nil && errors.Is(ctx.Err(), context.Canceled) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -2138,7 +2155,9 @@ func adjustNodeParams(nodeParams []NodeParam) ([]*nodesParam, error) {
|
|||
|
||||
// startRebalance runs loop to monitor connection healthy status.
|
||||
func (p *Pool) startRebalance(ctx context.Context) {
|
||||
ticker := time.NewTimer(p.rebalanceParams.clientRebalanceInterval)
|
||||
ticker := time.NewTicker(p.rebalanceParams.clientRebalanceInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
buffers := make([][]float64, len(p.rebalanceParams.nodesParams))
|
||||
for i, params := range p.rebalanceParams.nodesParams {
|
||||
buffers[i] = make([]float64, len(params.weights))
|
||||
|
@ -2188,7 +2207,7 @@ func (p *Pool) updateInnerNodesHealth(ctx context.Context, i int, bufferWeights
|
|||
tctx, c := context.WithTimeout(ctx, options.nodeRequestTimeout)
|
||||
defer c()
|
||||
|
||||
changed, err := cli.restartIfUnhealthy(tctx)
|
||||
changed, err := restartIfUnhealthy(tctx, cli)
|
||||
healthy := err == nil
|
||||
if healthy {
|
||||
bufferWeights[j] = options.nodesParams[i].weights[j]
|
||||
|
@ -2219,6 +2238,43 @@ func (p *Pool) updateInnerNodesHealth(ctx context.Context, i int, bufferWeights
|
|||
}
|
||||
}
|
||||
|
||||
// restartIfUnhealthy checks healthy status of client and recreate it if status is unhealthy.
|
||||
// Indicating if status was changed by this function call and returns error that caused unhealthy status.
|
||||
func restartIfUnhealthy(ctx context.Context, c client) (changed bool, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.setUnhealthy()
|
||||
} else {
|
||||
c.setHealthy()
|
||||
}
|
||||
}()
|
||||
|
||||
wasHealthy := c.isHealthy()
|
||||
|
||||
if res, err := c.healthcheck(ctx); err == nil {
|
||||
if res.Status().IsMaintenance() {
|
||||
return wasHealthy, new(apistatus.NodeUnderMaintenance)
|
||||
}
|
||||
|
||||
return !wasHealthy, nil
|
||||
}
|
||||
|
||||
if err = c.restart(ctx); err != nil {
|
||||
return wasHealthy, err
|
||||
}
|
||||
|
||||
res, err := c.healthcheck(ctx)
|
||||
if err != nil {
|
||||
return wasHealthy, err
|
||||
}
|
||||
|
||||
if res.Status().IsMaintenance() {
|
||||
return wasHealthy, new(apistatus.NodeUnderMaintenance)
|
||||
}
|
||||
|
||||
return !wasHealthy, nil
|
||||
}
|
||||
|
||||
func adjustWeights(weights []float64) []float64 {
|
||||
adjusted := make([]float64, len(weights))
|
||||
sum := 0.0
|
||||
|
@ -2256,7 +2312,7 @@ func (p *innerPool) connection() (client, error) {
|
|||
return nil, errors.New("no healthy client")
|
||||
}
|
||||
attempts := 3 * len(p.clients)
|
||||
for k := 0; k < attempts; k++ {
|
||||
for range attempts {
|
||||
i := p.sampler.Next()
|
||||
if cp := p.clients[i]; cp.isHealthy() {
|
||||
return cp, nil
|
||||
|
@ -2709,17 +2765,24 @@ func (p *Pool) ObjectRange(ctx context.Context, prm PrmObjectRange) (ResObjectRa
|
|||
// Must be initialized using Pool.SearchObjects, any other usage is unsafe.
|
||||
type ResObjectSearch struct {
|
||||
r *sdkClient.ObjectListReader
|
||||
handleError func(context.Context, apistatus.Status, error) error
|
||||
}
|
||||
|
||||
// Read reads another list of the object identifiers.
|
||||
func (x *ResObjectSearch) Read(buf []oid.ID) (int, error) {
|
||||
n, ok := x.r.Read(buf)
|
||||
if !ok {
|
||||
_, err := x.r.Close()
|
||||
res, err := x.r.Close()
|
||||
if err == nil {
|
||||
return n, io.EOF
|
||||
}
|
||||
|
||||
var status apistatus.Status
|
||||
if res != nil {
|
||||
status = res.Status()
|
||||
}
|
||||
err = x.handleError(nil, status, err)
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
|
@ -3017,12 +3080,10 @@ func (p *Pool) Close() {
|
|||
// close all clients
|
||||
for _, pools := range p.innerPools {
|
||||
for _, cli := range pools.clients {
|
||||
if cli.isDialed() {
|
||||
_ = cli.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SyncContainerWithNetwork applies network configuration received via
|
||||
// the Pool to the container. Changes the container if it does not satisfy
|
||||
|
|
|
@ -4,12 +4,13 @@ import (
|
|||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"strconv"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||
"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/session"
|
||||
|
@ -17,6 +18,8 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest"
|
||||
"go.uber.org/zap/zaptest/observer"
|
||||
)
|
||||
|
||||
func TestBuildPoolClientFailed(t *testing.T) {
|
||||
|
@ -222,7 +225,7 @@ func TestOneOfTwoFailed(t *testing.T) {
|
|||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
cp, err := pool.connection()
|
||||
require.NoError(t, err)
|
||||
st, _ := pool.cache.Get(formCacheKey(cp.address(), pool.key, false))
|
||||
|
@ -230,6 +233,179 @@ func TestOneOfTwoFailed(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUpdateNodesHealth(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
key := newPrivateKey(t)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
wasHealthy bool
|
||||
willHealthy bool
|
||||
prepareCli func(*mockClient)
|
||||
}{
|
||||
{
|
||||
name: "healthy, maintenance, unhealthy",
|
||||
wasHealthy: true,
|
||||
willHealthy: false,
|
||||
prepareCli: func(c *mockClient) { c.resOnEndpointInfo.SetStatus(netmap.Maintenance) },
|
||||
},
|
||||
{
|
||||
name: "unhealthy, maintenance, unhealthy",
|
||||
wasHealthy: false,
|
||||
willHealthy: false,
|
||||
prepareCli: func(c *mockClient) { c.resOnEndpointInfo.SetStatus(netmap.Maintenance) },
|
||||
},
|
||||
{
|
||||
name: "healthy, no error, healthy",
|
||||
wasHealthy: true,
|
||||
willHealthy: true,
|
||||
prepareCli: func(c *mockClient) { c.resOnEndpointInfo.SetStatus(netmap.Online) },
|
||||
},
|
||||
{
|
||||
name: "unhealthy, no error, healthy",
|
||||
wasHealthy: false,
|
||||
willHealthy: true,
|
||||
prepareCli: func(c *mockClient) { c.resOnEndpointInfo.SetStatus(netmap.Online) },
|
||||
},
|
||||
{
|
||||
name: "healthy, error, failed restart, unhealthy",
|
||||
wasHealthy: true,
|
||||
willHealthy: false,
|
||||
prepareCli: func(c *mockClient) {
|
||||
c.errOnEndpointInfo()
|
||||
c.errorOnDial = true
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unhealthy, error, failed restart, unhealthy",
|
||||
wasHealthy: false,
|
||||
willHealthy: false,
|
||||
prepareCli: func(c *mockClient) {
|
||||
c.errOnEndpointInfo()
|
||||
c.errorOnDial = true
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "healthy, error, restart, error, unhealthy",
|
||||
wasHealthy: true,
|
||||
willHealthy: false,
|
||||
prepareCli: func(c *mockClient) { c.errOnEndpointInfo() },
|
||||
},
|
||||
{
|
||||
name: "unhealthy, error, restart, error, unhealthy",
|
||||
wasHealthy: false,
|
||||
willHealthy: false,
|
||||
prepareCli: func(c *mockClient) { c.errOnEndpointInfo() },
|
||||
},
|
||||
{
|
||||
name: "healthy, error, restart, maintenance, unhealthy",
|
||||
wasHealthy: true,
|
||||
willHealthy: false,
|
||||
prepareCli: func(c *mockClient) {
|
||||
healthError := true
|
||||
c.healthcheckFn = func() {
|
||||
if healthError {
|
||||
c.errorOnEndpointInfo = errors.New("error")
|
||||
} else {
|
||||
c.errorOnEndpointInfo = nil
|
||||
c.resOnEndpointInfo.SetStatus(netmap.Maintenance)
|
||||
}
|
||||
healthError = !healthError
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unhealthy, error, restart, maintenance, unhealthy",
|
||||
wasHealthy: false,
|
||||
willHealthy: false,
|
||||
prepareCli: func(c *mockClient) {
|
||||
healthError := true
|
||||
c.healthcheckFn = func() {
|
||||
if healthError {
|
||||
c.errorOnEndpointInfo = errors.New("error")
|
||||
} else {
|
||||
c.errorOnEndpointInfo = nil
|
||||
c.resOnEndpointInfo.SetStatus(netmap.Maintenance)
|
||||
}
|
||||
healthError = !healthError
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "healthy, error, restart, healthy",
|
||||
wasHealthy: true,
|
||||
willHealthy: true,
|
||||
prepareCli: func(c *mockClient) {
|
||||
healthError := true
|
||||
c.healthcheckFn = func() {
|
||||
if healthError {
|
||||
c.errorOnEndpointInfo = errors.New("error")
|
||||
} else {
|
||||
c.errorOnEndpointInfo = nil
|
||||
}
|
||||
healthError = !healthError
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unhealthy, error, restart, healthy",
|
||||
wasHealthy: false,
|
||||
willHealthy: true,
|
||||
prepareCli: func(c *mockClient) {
|
||||
healthError := true
|
||||
c.healthcheckFn = func() {
|
||||
if healthError {
|
||||
c.errorOnEndpointInfo = errors.New("error")
|
||||
} else {
|
||||
c.errorOnEndpointInfo = nil
|
||||
}
|
||||
healthError = !healthError
|
||||
}
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := newMockClientHealthy("peer0", *key, tc.wasHealthy)
|
||||
tc.prepareCli(cli)
|
||||
p, log := newPool(t, cli)
|
||||
|
||||
p.updateNodesHealth(ctx, [][]float64{{1}})
|
||||
|
||||
changed := tc.wasHealthy != tc.willHealthy
|
||||
require.Equalf(t, tc.willHealthy, cli.isHealthy(), "healthy status should be: %v", tc.willHealthy)
|
||||
require.Equalf(t, changed, 1 == log.Len(), "healthy status should be changed: %v", changed)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newPool(t *testing.T, cli *mockClient) (*Pool, *observer.ObservedLogs) {
|
||||
log, observedLog := getObservedLogger()
|
||||
|
||||
cache, err := newCache(0)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &Pool{
|
||||
innerPools: []*innerPool{{
|
||||
sampler: newSampler([]float64{1}, rand.NewSource(0)),
|
||||
clients: []client{cli},
|
||||
}},
|
||||
cache: cache,
|
||||
key: newPrivateKey(t),
|
||||
closedCh: make(chan struct{}),
|
||||
rebalanceParams: rebalanceParameters{
|
||||
nodesParams: []*nodesParam{{1, []string{"peer0"}, []float64{1}}},
|
||||
nodeRequestTimeout: time.Second,
|
||||
clientRebalanceInterval: 200 * time.Millisecond,
|
||||
},
|
||||
logger: log,
|
||||
}, observedLog
|
||||
}
|
||||
|
||||
func getObservedLogger() (*zap.Logger, *observer.ObservedLogs) {
|
||||
loggerCore, observedLog := observer.New(zap.DebugLevel)
|
||||
return zap.New(loggerCore), observedLog
|
||||
}
|
||||
|
||||
func TestTwoFailed(t *testing.T) {
|
||||
var clientKeys []*ecdsa.PrivateKey
|
||||
mockClientBuilder := func(addr string) client {
|
||||
|
@ -514,7 +690,7 @@ func TestStatusMonitor(t *testing.T) {
|
|||
monitor.errorThreshold = 3
|
||||
|
||||
count := 10
|
||||
for i := 0; i < count; i++ {
|
||||
for range count {
|
||||
monitor.incErrorRate()
|
||||
}
|
||||
|
||||
|
@ -529,13 +705,6 @@ func TestStatusMonitor(t *testing.T) {
|
|||
isHealthy bool
|
||||
description string
|
||||
}{
|
||||
{
|
||||
action: func(m *clientStatusMonitor) { m.setUnhealthyOnDial() },
|
||||
status: statusUnhealthyOnDial,
|
||||
isDialed: false,
|
||||
isHealthy: false,
|
||||
description: "set unhealthy on dial",
|
||||
},
|
||||
{
|
||||
action: func(m *clientStatusMonitor) { m.setUnhealthy() },
|
||||
status: statusUnhealthyOnRequest,
|
||||
|
@ -554,7 +723,6 @@ func TestStatusMonitor(t *testing.T) {
|
|||
for _, tc := range cases {
|
||||
tc.action(&monitor)
|
||||
require.Equal(t, tc.status, monitor.healthy.Load())
|
||||
require.Equal(t, tc.isDialed, monitor.isDialed())
|
||||
require.Equal(t, tc.isHealthy, monitor.isHealthy())
|
||||
}
|
||||
})
|
||||
|
@ -562,19 +730,22 @@ func TestStatusMonitor(t *testing.T) {
|
|||
|
||||
func TestHandleError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
monitor := newClientStatusMonitor(zap.NewExample(), "", 10)
|
||||
log := zaptest.NewLogger(t)
|
||||
|
||||
canceledCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
for i, tc := range []struct {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
status apistatus.Status
|
||||
err error
|
||||
expectedError bool
|
||||
countError bool
|
||||
markedUnhealthy bool
|
||||
}{
|
||||
{
|
||||
name: "no error, no status",
|
||||
ctx: ctx,
|
||||
status: nil,
|
||||
err: nil,
|
||||
|
@ -582,6 +753,7 @@ func TestHandleError(t *testing.T) {
|
|||
countError: false,
|
||||
},
|
||||
{
|
||||
name: "no error, success status",
|
||||
ctx: ctx,
|
||||
status: new(apistatus.SuccessDefaultV2),
|
||||
err: nil,
|
||||
|
@ -589,6 +761,7 @@ func TestHandleError(t *testing.T) {
|
|||
countError: false,
|
||||
},
|
||||
{
|
||||
name: "error, success status",
|
||||
ctx: ctx,
|
||||
status: new(apistatus.SuccessDefaultV2),
|
||||
err: errors.New("error"),
|
||||
|
@ -596,6 +769,7 @@ func TestHandleError(t *testing.T) {
|
|||
countError: true,
|
||||
},
|
||||
{
|
||||
name: "error, no status",
|
||||
ctx: ctx,
|
||||
status: nil,
|
||||
err: errors.New("error"),
|
||||
|
@ -603,6 +777,7 @@ func TestHandleError(t *testing.T) {
|
|||
countError: true,
|
||||
},
|
||||
{
|
||||
name: "no error, object not found status",
|
||||
ctx: ctx,
|
||||
status: new(apistatus.ObjectNotFound),
|
||||
err: nil,
|
||||
|
@ -610,6 +785,7 @@ func TestHandleError(t *testing.T) {
|
|||
countError: false,
|
||||
},
|
||||
{
|
||||
name: "object not found error, object not found status",
|
||||
ctx: ctx,
|
||||
status: new(apistatus.ObjectNotFound),
|
||||
err: &apistatus.ObjectNotFound{},
|
||||
|
@ -617,6 +793,7 @@ func TestHandleError(t *testing.T) {
|
|||
countError: false,
|
||||
},
|
||||
{
|
||||
name: "eacl not found error, no status",
|
||||
ctx: ctx,
|
||||
status: nil,
|
||||
err: &apistatus.EACLNotFound{},
|
||||
|
@ -627,6 +804,7 @@ func TestHandleError(t *testing.T) {
|
|||
countError: true,
|
||||
},
|
||||
{
|
||||
name: "no error, internal status",
|
||||
ctx: ctx,
|
||||
status: new(apistatus.ServerInternal),
|
||||
err: nil,
|
||||
|
@ -634,6 +812,7 @@ func TestHandleError(t *testing.T) {
|
|||
countError: true,
|
||||
},
|
||||
{
|
||||
name: "no error, wrong magic status",
|
||||
ctx: ctx,
|
||||
status: new(apistatus.WrongMagicNumber),
|
||||
err: nil,
|
||||
|
@ -641,6 +820,7 @@ func TestHandleError(t *testing.T) {
|
|||
countError: true,
|
||||
},
|
||||
{
|
||||
name: "no error, signature verification status",
|
||||
ctx: ctx,
|
||||
status: new(apistatus.SignatureVerification),
|
||||
err: nil,
|
||||
|
@ -648,20 +828,33 @@ func TestHandleError(t *testing.T) {
|
|||
countError: true,
|
||||
},
|
||||
{
|
||||
ctx: ctx,
|
||||
status: new(apistatus.SignatureVerification),
|
||||
err: nil,
|
||||
expectedError: true,
|
||||
countError: true,
|
||||
},
|
||||
{
|
||||
name: "no error, maintenance status",
|
||||
ctx: ctx,
|
||||
status: new(apistatus.NodeUnderMaintenance),
|
||||
err: nil,
|
||||
expectedError: true,
|
||||
countError: true,
|
||||
markedUnhealthy: true,
|
||||
},
|
||||
{
|
||||
name: "maintenance error, no status",
|
||||
ctx: ctx,
|
||||
status: nil,
|
||||
err: &apistatus.NodeUnderMaintenance{},
|
||||
expectedError: true,
|
||||
countError: true,
|
||||
markedUnhealthy: true,
|
||||
},
|
||||
{
|
||||
name: "no error, invalid argument status",
|
||||
ctx: ctx,
|
||||
status: new(apistatus.InvalidArgument),
|
||||
err: nil,
|
||||
expectedError: true,
|
||||
countError: false,
|
||||
},
|
||||
{
|
||||
name: "context canceled error, no status",
|
||||
ctx: canceledCtx,
|
||||
status: nil,
|
||||
err: errors.New("error"),
|
||||
|
@ -669,8 +862,9 @@ func TestHandleError(t *testing.T) {
|
|||
countError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
errCount := monitor.currentErrorRate()
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
monitor := newClientStatusMonitor(log, "", 10)
|
||||
errCount := monitor.overallErrorRate()
|
||||
err := monitor.handleError(tc.ctx, tc.status, tc.err)
|
||||
if tc.expectedError {
|
||||
require.Error(t, err)
|
||||
|
@ -680,7 +874,10 @@ func TestHandleError(t *testing.T) {
|
|||
if tc.countError {
|
||||
errCount++
|
||||
}
|
||||
require.Equal(t, errCount, monitor.currentErrorRate())
|
||||
require.Equal(t, errCount, monitor.overallErrorRate())
|
||||
if tc.markedUnhealthy {
|
||||
require.False(t, monitor.isHealthy())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -724,7 +921,7 @@ func TestSwitchAfterErrorThreshold(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
t.Cleanup(pool.Close)
|
||||
|
||||
for i := 0; i < errorThreshold; i++ {
|
||||
for range errorThreshold {
|
||||
conn, err := pool.connection()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, nodes[0].address, conn.address())
|
||||
|
|
|
@ -30,7 +30,7 @@ func newSampler(probabilities []float64, source rand.Source) *sampler {
|
|||
sampler.alias = make([]int, n)
|
||||
// Compute scaled probabilities.
|
||||
p := make([]float64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
p[i] = probabilities[i] * float64(n)
|
||||
}
|
||||
for i, pi := range p {
|
||||
|
|
|
@ -32,7 +32,7 @@ func TestSamplerStability(t *testing.T) {
|
|||
for _, tc := range cases {
|
||||
sampler := newSampler(tc.probabilities, rand.NewSource(0))
|
||||
res := make([]int, len(tc.probabilities))
|
||||
for i := 0; i < COUNT; i++ {
|
||||
for range COUNT {
|
||||
res[sampler.Next()]++
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ func (c *treeClient) dial(ctx context.Context) error {
|
|||
}
|
||||
|
||||
var err error
|
||||
if c.conn, c.service, err = dialClient(ctx, c.address, c.opts...); err != nil {
|
||||
if c.conn, c.service, err = createClient(c.address, c.opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ func (c *treeClient) redialIfNecessary(ctx context.Context) (healthHasChanged bo
|
|||
defer c.mu.Unlock()
|
||||
|
||||
if c.conn == nil {
|
||||
if c.conn, c.service, err = dialClient(ctx, c.address, c.opts...); err != nil {
|
||||
if c.conn, c.service, err = createClient(c.address, c.opts...); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ func (c *treeClient) redialIfNecessary(ctx context.Context) (healthHasChanged bo
|
|||
return !wasHealthy, nil
|
||||
}
|
||||
|
||||
func dialClient(ctx context.Context, addr string, clientOptions ...grpc.DialOption) (*grpc.ClientConn, grpcService.TreeServiceClient, error) {
|
||||
func createClient(addr string, clientOptions ...grpc.DialOption) (*grpc.ClientConn, grpcService.TreeServiceClient, error) {
|
||||
host, tlsEnable, err := apiClient.ParseURI(addr)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parse address: %w", err)
|
||||
|
@ -93,9 +93,9 @@ func dialClient(ctx context.Context, addr string, clientOptions ...grpc.DialOpti
|
|||
// the order is matter, we want client to be able to overwrite options.
|
||||
opts := append(options, clientOptions...)
|
||||
|
||||
conn, err := grpc.DialContext(ctx, host, opts...)
|
||||
conn, err := grpc.NewClient(host, opts...)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("grpc dial node tree service: %w", err)
|
||||
return nil, nil, fmt.Errorf("grpc create node tree service: %w", err)
|
||||
}
|
||||
|
||||
return conn, grpcService.NewTreeServiceClient(conn), nil
|
||||
|
|
110
pool/tree/network/address.go
Normal file
110
pool/tree/network/address.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
manet "github.com/multiformats/go-multiaddr/net"
|
||||
)
|
||||
|
||||
var errHostIsEmpty = errors.New("host is empty")
|
||||
|
||||
// Address represents the FrostFS node
|
||||
// network address.
|
||||
type Address struct {
|
||||
ma multiaddr.Multiaddr
|
||||
}
|
||||
|
||||
// URIAddr returns Address as a URI.
|
||||
//
|
||||
// Panics if host address cannot be fetched from Address.
|
||||
//
|
||||
// See also FromString.
|
||||
func (a Address) URIAddr() string {
|
||||
_, host, err := manet.DialArgs(a.ma)
|
||||
if err != nil {
|
||||
// the only correct way to construct Address is AddressFromString
|
||||
// which makes this error appear unexpected
|
||||
panic(fmt.Errorf("could not get host addr: %w", err))
|
||||
}
|
||||
|
||||
if !a.IsTLSEnabled() {
|
||||
return host
|
||||
}
|
||||
|
||||
return (&url.URL{
|
||||
Scheme: "grpcs",
|
||||
Host: host,
|
||||
}).String()
|
||||
}
|
||||
|
||||
// FromString restores Address from a string representation.
|
||||
//
|
||||
// Supports URIAddr, MultiAddr and HostAddr strings.
|
||||
func (a *Address) FromString(s string) error {
|
||||
var err error
|
||||
|
||||
a.ma, err = multiaddr.NewMultiaddr(s)
|
||||
if err != nil {
|
||||
var (
|
||||
host string
|
||||
hasTLS bool
|
||||
)
|
||||
host, hasTLS, err = client.ParseURI(s)
|
||||
if err != nil {
|
||||
host = s
|
||||
}
|
||||
|
||||
s, err = multiaddrStringFromHostAddr(host)
|
||||
if err == nil {
|
||||
a.ma, err = multiaddr.NewMultiaddr(s)
|
||||
if err == nil && hasTLS {
|
||||
a.ma = a.ma.Encapsulate(tls)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// multiaddrStringFromHostAddr converts "localhost:8080" to "/dns4/localhost/tcp/8080".
|
||||
func multiaddrStringFromHostAddr(host string) (string, error) {
|
||||
if len(host) == 0 {
|
||||
return "", errHostIsEmpty
|
||||
}
|
||||
|
||||
endpoint, port, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Empty address in host `:8080` generates `/dns4//tcp/8080` multiaddr
|
||||
// which is invalid. It could be `/tcp/8080` but this breaks
|
||||
// `manet.DialArgs`. The solution is to manually parse it as 0.0.0.0
|
||||
if endpoint == "" {
|
||||
return "/ip4/0.0.0.0/tcp/" + port, nil
|
||||
}
|
||||
|
||||
var (
|
||||
prefix = "/dns4"
|
||||
addr = endpoint
|
||||
)
|
||||
|
||||
if ip := net.ParseIP(endpoint); ip != nil {
|
||||
addr = ip.String()
|
||||
if ip.To4() == nil {
|
||||
prefix = "/ip6"
|
||||
} else {
|
||||
prefix = "/ip4"
|
||||
}
|
||||
}
|
||||
|
||||
const l4Protocol = "tcp"
|
||||
|
||||
return strings.Join([]string{prefix, addr, l4Protocol, port}, "/"), nil
|
||||
}
|
16
pool/tree/network/tls.go
Normal file
16
pool/tree/network/tls.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package network
|
||||
|
||||
import "github.com/multiformats/go-multiaddr"
|
||||
|
||||
const (
|
||||
tlsProtocolName = "tls"
|
||||
)
|
||||
|
||||
// tls var is used for (un)wrapping other multiaddrs around TLS multiaddr.
|
||||
var tls, _ = multiaddr.NewMultiaddr("/" + tlsProtocolName)
|
||||
|
||||
// IsTLSEnabled searches for wrapped TLS protocol in multiaddr.
|
||||
func (a Address) IsTLSEnabled() bool {
|
||||
_, err := a.ma.ValueForProtocol(multiaddr.P_TLS)
|
||||
return err == nil
|
||||
}
|
|
@ -10,8 +10,11 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
clientSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
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"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree/network"
|
||||
grpcService "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree/service"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"go.uber.org/zap"
|
||||
|
@ -71,6 +74,8 @@ type InitParameters struct {
|
|||
nodeParams []pool.NodeParam
|
||||
dialOptions []grpc.DialOption
|
||||
maxRequestAttempts int
|
||||
|
||||
bootstrapAddr string
|
||||
}
|
||||
|
||||
// Pool represents virtual connection to the FrostFS tree services network to communicate
|
||||
|
@ -99,6 +104,10 @@ type Pool struct {
|
|||
// * rebalance procedure (see Pool.startRebalance)
|
||||
// * retry in case of request failure (see Pool.requestWithRetry)
|
||||
startIndices [2]int
|
||||
|
||||
bootstrapAddr string
|
||||
clientMap map[uint64]client
|
||||
netMap netmap.NetMap
|
||||
}
|
||||
|
||||
type innerPool struct {
|
||||
|
@ -121,6 +130,7 @@ type GetNodesParams struct {
|
|||
LatestOnly bool
|
||||
AllAttrs bool
|
||||
BearerToken []byte
|
||||
Policy netmap.PlacementPolicy
|
||||
}
|
||||
|
||||
// GetSubTreeParams groups parameters of Pool.GetSubTree operation.
|
||||
|
@ -131,6 +141,7 @@ type GetSubTreeParams struct {
|
|||
Depth uint32
|
||||
BearerToken []byte
|
||||
Order SubTreeSort
|
||||
Policy netmap.PlacementPolicy
|
||||
}
|
||||
|
||||
// AddNodeParams groups parameters of Pool.AddNode operation.
|
||||
|
@ -140,6 +151,7 @@ type AddNodeParams struct {
|
|||
Parent uint64
|
||||
Meta map[string]string
|
||||
BearerToken []byte
|
||||
Policy netmap.PlacementPolicy
|
||||
}
|
||||
|
||||
// AddNodeByPathParams groups parameters of Pool.AddNodeByPath operation.
|
||||
|
@ -150,6 +162,7 @@ type AddNodeByPathParams struct {
|
|||
Meta map[string]string
|
||||
PathAttribute string
|
||||
BearerToken []byte
|
||||
Policy netmap.PlacementPolicy
|
||||
}
|
||||
|
||||
// MoveNodeParams groups parameters of Pool.MoveNode operation.
|
||||
|
@ -160,6 +173,7 @@ type MoveNodeParams struct {
|
|||
ParentID uint64
|
||||
Meta map[string]string
|
||||
BearerToken []byte
|
||||
Policy netmap.PlacementPolicy
|
||||
}
|
||||
|
||||
// RemoveNodeParams groups parameters of Pool.RemoveNode operation.
|
||||
|
@ -168,6 +182,7 @@ type RemoveNodeParams struct {
|
|||
TreeID string
|
||||
NodeID uint64
|
||||
BearerToken []byte
|
||||
Policy netmap.PlacementPolicy
|
||||
}
|
||||
|
||||
// MethodIndex index of method in list of statuses in Pool.
|
||||
|
@ -208,6 +223,9 @@ func NewPool(options InitParameters) (*Pool, error) {
|
|||
if options.key == nil {
|
||||
return nil, fmt.Errorf("missed required parameter 'Key'")
|
||||
}
|
||||
if options.bootstrapAddr == "" {
|
||||
return nil, fmt.Errorf("missed bootstrap address")
|
||||
}
|
||||
|
||||
nodesParams, err := adjustNodeParams(options.nodeParams)
|
||||
if err != nil {
|
||||
|
@ -232,6 +250,7 @@ func NewPool(options InitParameters) (*Pool, error) {
|
|||
},
|
||||
maxRequestAttempts: options.maxRequestAttempts,
|
||||
methods: methods,
|
||||
bootstrapAddr: options.bootstrapAddr,
|
||||
}
|
||||
|
||||
return p, nil
|
||||
|
@ -246,24 +265,37 @@ func NewPool(options InitParameters) (*Pool, error) {
|
|||
//
|
||||
// See also InitParameters.SetClientRebalanceInterval.
|
||||
func (p *Pool) Dial(ctx context.Context) error {
|
||||
inner := make([]*innerPool, len(p.rebalanceParams.nodesGroup))
|
||||
var atLeastOneHealthy bool
|
||||
cl, err := p.getNetMapClient(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get netmap client: %w", err)
|
||||
}
|
||||
|
||||
for i, nodes := range p.rebalanceParams.nodesGroup {
|
||||
clients := make([]client, len(nodes))
|
||||
for j, node := range nodes {
|
||||
clients[j] = newTreeClient(node.Address(), p.dialOptions...)
|
||||
if err := clients[j].dial(ctx); err != nil {
|
||||
p.log(zap.WarnLevel, "failed to dial tree client", zap.String("address", node.Address()), zap.Error(err))
|
||||
continue
|
||||
res, err := cl.NetMapSnapshot(ctx, clientSDK.PrmNetMapSnapshot{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get netmap: %w", err)
|
||||
}
|
||||
p.netMap = res.NetMap()
|
||||
|
||||
p.clientMap = make(map[uint64]client, len(p.netMap.Nodes()))
|
||||
var atLeastOneHealthy bool
|
||||
for _, node := range p.netMap.Nodes() {
|
||||
node.IterateNetworkEndpoints(func(endpoint string) bool {
|
||||
var addr network.Address
|
||||
if err := addr.FromString(endpoint); err != nil {
|
||||
p.log(zap.WarnLevel, "can't parse endpoint", zap.String("endpoint", endpoint), zap.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
treeClient := newTreeClient(addr.URIAddr(), p.dialOptions...)
|
||||
if err := treeClient.dial(ctx); err != nil {
|
||||
p.log(zap.WarnLevel, "failed to dial tree client", zap.String("address", addr.URIAddr()), zap.Error(err))
|
||||
return false
|
||||
}
|
||||
|
||||
atLeastOneHealthy = true
|
||||
}
|
||||
|
||||
inner[i] = &innerPool{
|
||||
clients: clients,
|
||||
}
|
||||
p.clientMap[node.Hash()] = treeClient
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
if !atLeastOneHealthy {
|
||||
|
@ -273,12 +305,31 @@ func (p *Pool) Dial(ctx context.Context) error {
|
|||
ctx, cancel := context.WithCancel(ctx)
|
||||
p.cancel = cancel
|
||||
p.closedCh = make(chan struct{})
|
||||
p.innerPools = inner
|
||||
|
||||
go p.startRebalance(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pool) getNetMapClient(ctx context.Context) (*clientSDK.Client, error) {
|
||||
var c clientSDK.Client
|
||||
|
||||
prmInit := clientSDK.PrmInit{
|
||||
Key: p.key.PrivateKey,
|
||||
}
|
||||
|
||||
prmDial := clientSDK.PrmDial{
|
||||
Endpoint: p.bootstrapAddr,
|
||||
}
|
||||
|
||||
c.Init(prmInit)
|
||||
|
||||
if err := c.Dial(ctx, prmDial); err != nil {
|
||||
return nil, fmt.Errorf("dial client: %w", err)
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// SetKey specifies default key to be used for the protocol communication by default.
|
||||
func (x *InitParameters) SetKey(key *keys.PrivateKey) {
|
||||
x.key = key
|
||||
|
@ -329,6 +380,10 @@ func (x *InitParameters) SetMaxRequestAttempts(maxAttempts int) {
|
|||
x.maxRequestAttempts = maxAttempts
|
||||
}
|
||||
|
||||
func (x *InitParameters) SetBootstrapAddress(addr string) {
|
||||
x.bootstrapAddr = addr
|
||||
}
|
||||
|
||||
// GetNodes invokes eponymous method from TreeServiceClient.
|
||||
//
|
||||
// Can return predefined errors:
|
||||
|
@ -354,13 +409,13 @@ func (p *Pool) GetNodes(ctx context.Context, prm GetNodesParams) ([]*grpcService
|
|||
}
|
||||
|
||||
var resp *grpcService.GetNodeByPathResponse
|
||||
err := p.requestWithRetry(ctx, func(client grpcService.TreeServiceClient) (inErr error) {
|
||||
err := p.newRequestWithRetry(ctx, prm.CID[:], prm.Policy, func(client grpcService.TreeServiceClient) (inErr error) {
|
||||
resp, inErr = client.GetNodeByPath(ctx, request)
|
||||
// Pool wants to do retry 'GetNodeByPath' request if result is empty.
|
||||
// Empty result is expected due to delayed tree service sync.
|
||||
// Return an error there to trigger retry and ignore it after,
|
||||
// to keep compatibility with 'GetNodeByPath' implementation.
|
||||
if inErr == nil && len(resp.Body.Nodes) == 0 {
|
||||
if inErr == nil && len(resp.GetBody().GetNodes()) == 0 {
|
||||
return errNodeEmptyResult
|
||||
}
|
||||
return handleError("failed to get node by path", inErr)
|
||||
|
@ -382,14 +437,14 @@ type SubTreeReader struct {
|
|||
|
||||
// Read reads another list of the subtree nodes.
|
||||
func (x *SubTreeReader) Read(buf []*grpcService.GetSubTreeResponse_Body) (int, error) {
|
||||
for i := 0; i < len(buf); i++ {
|
||||
for i := range len(buf) {
|
||||
resp, err := x.cli.Recv()
|
||||
if err == io.EOF {
|
||||
return i, io.EOF
|
||||
} else if err != nil {
|
||||
return i, handleError("failed to get sub tree", err)
|
||||
}
|
||||
buf[i] = resp.Body
|
||||
buf[i] = resp.GetBody()
|
||||
}
|
||||
|
||||
return len(buf), nil
|
||||
|
@ -405,7 +460,7 @@ func (x *SubTreeReader) ReadAll() ([]*grpcService.GetSubTreeResponse_Body, error
|
|||
} else if err != nil {
|
||||
return nil, handleError("failed to get sub tree", err)
|
||||
}
|
||||
res = append(res, resp.Body)
|
||||
res = append(res, resp.GetBody())
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
@ -421,7 +476,7 @@ func (x *SubTreeReader) Next() (*grpcService.GetSubTreeResponse_Body, error) {
|
|||
return nil, handleError("failed to get sub tree", err)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
return resp.GetBody(), nil
|
||||
}
|
||||
|
||||
// GetSubTree invokes eponymous method from TreeServiceClient.
|
||||
|
@ -454,7 +509,7 @@ func (p *Pool) GetSubTree(ctx context.Context, prm GetSubTreeParams) (*SubTreeRe
|
|||
}
|
||||
|
||||
var cli grpcService.TreeService_GetSubTreeClient
|
||||
err := p.requestWithRetry(ctx, func(client grpcService.TreeServiceClient) (inErr error) {
|
||||
err := p.newRequestWithRetry(ctx, prm.CID[:], prm.Policy, func(client grpcService.TreeServiceClient) (inErr error) {
|
||||
cli, inErr = client.GetSubTree(ctx, request)
|
||||
return handleError("failed to get sub tree client", inErr)
|
||||
})
|
||||
|
@ -488,7 +543,7 @@ func (p *Pool) AddNode(ctx context.Context, prm AddNodeParams) (uint64, error) {
|
|||
}
|
||||
|
||||
var resp *grpcService.AddResponse
|
||||
err := p.requestWithRetry(ctx, func(client grpcService.TreeServiceClient) (inErr error) {
|
||||
err := p.newRequestWithRetry(ctx, prm.CID[:], prm.Policy, func(client grpcService.TreeServiceClient) (inErr error) {
|
||||
resp, inErr = client.Add(ctx, request)
|
||||
return handleError("failed to add node", inErr)
|
||||
})
|
||||
|
@ -523,7 +578,7 @@ func (p *Pool) AddNodeByPath(ctx context.Context, prm AddNodeByPathParams) (uint
|
|||
}
|
||||
|
||||
var resp *grpcService.AddByPathResponse
|
||||
err := p.requestWithRetry(ctx, func(client grpcService.TreeServiceClient) (inErr error) {
|
||||
err := p.newRequestWithRetry(ctx, prm.CID[:], prm.Policy, func(client grpcService.TreeServiceClient) (inErr error) {
|
||||
resp, inErr = client.AddByPath(ctx, request)
|
||||
return handleError("failed to add node by path", inErr)
|
||||
})
|
||||
|
@ -535,12 +590,12 @@ func (p *Pool) AddNodeByPath(ctx context.Context, prm AddNodeByPathParams) (uint
|
|||
body := resp.GetBody()
|
||||
if body == nil {
|
||||
return 0, errors.New("nil body in tree service response")
|
||||
} else if len(body.Nodes) == 0 {
|
||||
} else if len(body.GetNodes()) == 0 {
|
||||
return 0, errors.New("empty list of added nodes in tree service response")
|
||||
}
|
||||
|
||||
// The first node is the leaf that we add, according to tree service docs.
|
||||
return body.Nodes[0], nil
|
||||
return body.GetNodes()[0], nil
|
||||
}
|
||||
|
||||
// MoveNode invokes eponymous method from TreeServiceClient.
|
||||
|
@ -565,7 +620,7 @@ func (p *Pool) MoveNode(ctx context.Context, prm MoveNodeParams) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err := p.requestWithRetry(ctx, func(client grpcService.TreeServiceClient) error {
|
||||
err := p.newRequestWithRetry(ctx, prm.CID[:], prm.Policy, func(client grpcService.TreeServiceClient) error {
|
||||
if _, err := client.Move(ctx, request); err != nil {
|
||||
return handleError("failed to move node", err)
|
||||
}
|
||||
|
@ -596,7 +651,7 @@ func (p *Pool) RemoveNode(ctx context.Context, prm RemoveNodeParams) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err := p.requestWithRetry(ctx, func(client grpcService.TreeServiceClient) error {
|
||||
err := p.newRequestWithRetry(ctx, prm.CID[:], prm.Policy, func(client grpcService.TreeServiceClient) error {
|
||||
if _, err := client.Remove(ctx, request); err != nil {
|
||||
return handleError("failed to remove node", err)
|
||||
}
|
||||
|
@ -613,14 +668,12 @@ func (p *Pool) Close() error {
|
|||
<-p.closedCh
|
||||
|
||||
var err error
|
||||
for _, group := range p.innerPools {
|
||||
for _, cl := range group.clients {
|
||||
for _, cl := range p.clientMap {
|
||||
if closeErr := cl.close(); closeErr != nil {
|
||||
p.log(zapcore.ErrorLevel, "close client connection", zap.Error(closeErr))
|
||||
p.log(zapcore.ErrorLevel, "close client connection", zap.String("endpoint", cl.endpoint()), zap.Error(closeErr))
|
||||
err = closeErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -854,6 +907,63 @@ LOOP:
|
|||
return finErr
|
||||
}
|
||||
|
||||
func (p *Pool) newRequestWithRetry(ctx context.Context, cid []byte, policy netmap.PlacementPolicy, fn func(client grpcService.TreeServiceClient) error) error {
|
||||
var (
|
||||
err, finErr error
|
||||
cl grpcService.TreeServiceClient
|
||||
)
|
||||
|
||||
reqID := GetRequestID(ctx)
|
||||
|
||||
cntNodes, err := p.netMap.ContainerNodes(policy, cid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get container nodes: %w", err)
|
||||
}
|
||||
|
||||
cntNodes, err = p.netMap.PlacementVectors(cntNodes, cid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get placement vectors: %w", err)
|
||||
}
|
||||
|
||||
attempts := p.maxRequestAttempts
|
||||
|
||||
LOOP:
|
||||
for i := 0; i < len(cntNodes); i++ {
|
||||
clientsLen := len(cntNodes[i])
|
||||
for j := 0; j < clientsLen; j++ {
|
||||
if attempts == 0 {
|
||||
break LOOP
|
||||
}
|
||||
attempts--
|
||||
|
||||
client, ok := p.clientMap[cntNodes[i][j].Hash()]
|
||||
if !ok {
|
||||
finErr = finalError(finErr, errors.New("missed node client"))
|
||||
p.log(zap.DebugLevel, "missed node client", zap.String("request_id", reqID), zap.Int("remaining attempts", attempts),
|
||||
zap.Uint64("node hash", cntNodes[i][j].Hash()))
|
||||
continue
|
||||
}
|
||||
|
||||
if cl, err = client.serviceClient(); err == nil {
|
||||
err = fn(cl)
|
||||
}
|
||||
if !shouldTryAgain(err) {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("address %s: %w", client.endpoint(), err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
finErr = finalError(finErr, err)
|
||||
p.log(zap.DebugLevel, "tree request error", zap.String("request_id", reqID), zap.Int("remaining attempts", attempts),
|
||||
zap.String("address", client.endpoint()), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
return finErr
|
||||
}
|
||||
|
||||
func shouldTryAgain(err error) bool {
|
||||
return !(err == nil || errors.Is(err, ErrNodeAccessDenied))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue