frostfs-node/services/public/object/status.go

952 lines
19 KiB
Go
Raw Normal View History

package object
import (
"fmt"
"sync"
"github.com/golang/protobuf/proto"
"github.com/nspcc-dev/neofs-api-go/object"
"github.com/nspcc-dev/neofs-api-go/session"
"github.com/nspcc-dev/neofs-node/internal"
"github.com/nspcc-dev/neofs-node/lib/implementations"
"github.com/nspcc-dev/neofs-node/lib/localstore"
"github.com/nspcc-dev/neofs-node/lib/transformer"
"github.com/pkg/errors"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// group of value for status error construction.
type statusInfo struct {
// status code
c codes.Code
// error message
m string
// error details
d []proto.Message
}
type requestError struct {
// type of request
t object.RequestType
// request handler error
e error
}
// error implementation used for details attaching.
type detailedError struct {
error
d []proto.Message
}
type statusCalculator struct {
*sync.RWMutex
common map[error]*statusInfo
custom map[requestError]*statusInfo
}
const panicLogMsg = "rpc handler caused panic"
const (
msgServerPanic = "panic occurred during request processing"
errServerPanic = internal.Error("panic on call handler")
)
const (
msgUnauthenticated = "request does not have valid authentication credentials for the operation"
errUnauthenticated = internal.Error("unauthenticated request")
)
const (
msgReSigning = "server could not re-sign request"
errReSigning = internal.Error("could not re-sign request")
)
const (
msgInvalidTTL = "invalid TTL value"
errInvalidTTL = internal.Error("invalid TTL value")
)
const (
msgNotLocalContainer = "server is not presented in container"
errNotLocalContainer = internal.Error("not local container")
descNotLocalContainer = "server is outside container"
)
const (
msgContainerAffiliationProblem = "server could not check container affiliation"
errContainerAffiliationProblem = internal.Error("could not check container affiliation")
)
const (
msgContainerNotFound = "container not found"
errContainerNotFound = internal.Error("container not found")
descContainerNotFound = "handling a non-existent container"
)
const (
msgPlacementProblem = "there were problems building the placement vector on the server"
errPlacementProblem = internal.Error("could not traverse over container")
)
const (
msgOverloaded = "system resource overloaded"
errOverloaded = internal.Error("system resource overloaded")
)
const (
msgAccessDenied = "access to requested operation is denied"
errAccessDenied = internal.Error("access denied")
)
const (
msgPutMessageProblem = "invalid message type"
msgPutNilObject = "object is null"
)
const (
msgCutObjectPayload = "lack of object payload data"
)
const (
msgMissingTokenKeys = "missing public keys in token"
msgBrokenToken = "token structure failed verification"
msgTokenObjectID = "missing object ID in token"
)
const (
msgProcPayloadSize = "max payload size of processing object overflow"
errProcPayloadSize = internal.Error("max processing object payload size overflow")
)
const (
msgObjectCreationEpoch = "invalid creation epoch of object"
errObjectFromTheFuture = internal.Error("object from the future")
)
const (
msgObjectPayloadSize = "max object payload size overflow"
errObjectPayloadSize = internal.Error("max object payload size overflow")
)
const (
msgLocalStorageOverflow = "not enough space in local storage"
errLocalStorageOverflow = internal.Error("local storage overflow")
)
const (
msgPayloadChecksum = "invalid payload checksum"
errPayloadChecksum = internal.Error("invalid payload checksum")
)
const (
msgObjectHeadersVerification = "object headers failed verification"
errObjectHeadersVerification = internal.Error("object headers failed verification")
)
const (
msgForwardPutObject = "forward object failure"
)
const (
msgPutLocalFailure = "local object put failure"
errPutLocal = internal.Error("local object put failure")
)
const (
msgPrivateTokenRecv = "private token receive failure"
)
const (
msgInvalidSGLinking = "invalid storage group headers"
)
const (
msgIncompleteSGInfo = "collect storage group info failure"
)
const (
msgTransformationFailure = "object preparation failure"
)
const (
msgWrongSGSize = "wrong storage group size"
errWrongSGSize = internal.Error("wrong storage group size")
)
const (
msgWrongSGHash = "wrong storage group homomorphic hash"
errWrongSGHash = internal.Error("wrong storage group homomorphic hash")
)
const (
msgObjectNotFound = "object not found"
)
const (
msgObjectHeaderNotFound = "object header not found"
)
const (
msgNonAssembly = "assembly option is not enabled on the server"
)
const (
msgPayloadOutOfRange = "range is out of object payload bounds"
)
const (
msgPayloadRangeNotFound = "object payload range not found"
errPayloadRangeNotFound = internal.Error("object payload range not found")
)
const (
msgMissingToken = "missing token in request"
)
const (
msgPutTombstone = "could not store tombstone"
)
const (
msgDeletePrepare = "delete information preparation failure"
errDeletePrepare = internal.Error("delete information preparation failure")
)
const (
msgQueryVersion = "unsupported query version"
)
const (
msgSearchQueryUnmarshal = "query unmarshal failure"
)
const (
msgLocalQueryImpose = "local query imposing failure"
)
var mStatusCommon = map[error]*statusInfo{
// RPC implementation recovered panic
errServerPanic: {
c: codes.Internal,
m: msgServerPanic,
},
// Request authentication credentials problem
errUnauthenticated: {
c: codes.Unauthenticated,
m: msgUnauthenticated,
d: requestAuthDetails(),
},
// Request re-signing problem
errReSigning: {
c: codes.Internal,
m: msgReSigning,
},
// Invalid request TTL
errInvalidTTL: {
c: codes.InvalidArgument,
m: msgInvalidTTL,
d: invalidTTLDetails(),
},
// Container affiliation check problem
errContainerAffiliationProblem: {
c: codes.Internal,
m: msgContainerAffiliationProblem,
},
// Server is outside container
errNotLocalContainer: {
c: codes.FailedPrecondition,
m: msgNotLocalContainer,
d: containerAbsenceDetails(),
},
// Container not found in storage
errContainerNotFound: {
c: codes.NotFound,
m: msgContainerNotFound,
},
// Container placement build problem
errPlacementProblem: {
c: codes.Internal,
m: msgPlacementProblem,
},
// System resource overloaded
errOverloaded: {
c: codes.Unavailable,
m: msgOverloaded,
},
// Access violations
errAccessDenied: {
c: codes.PermissionDenied,
m: msgAccessDenied,
},
// Maximum processing payload size overflow
errProcPayloadSize: {
c: codes.FailedPrecondition,
m: msgProcPayloadSize,
d: nil, // TODO: NSPCC-1048
},
}
var mStatusCustom = map[requestError]*statusInfo{
// Invalid first message in Put client stream
{
t: object.RequestPut,
e: errHeaderExpected,
}: {
c: codes.InvalidArgument,
m: msgPutMessageProblem,
d: putFirstMessageDetails(),
},
// Nil object in Put request
{
t: object.RequestPut,
e: errObjectExpected,
}: {
c: codes.InvalidArgument,
m: msgPutNilObject,
d: putNilObjectDetails(),
},
// Lack of object payload data
{
t: object.RequestPut,
e: transformer.ErrPayloadEOF,
}: {
c: codes.InvalidArgument,
m: msgCutObjectPayload,
d: payloadSizeDetails(),
},
// Lack of public keys in the token
{
t: object.RequestPut,
e: errMissingOwnerKeys,
}: {
c: codes.PermissionDenied,
m: msgMissingTokenKeys,
d: tokenKeysDetails(),
},
// Broken token structure
{
t: object.RequestPut,
e: errBrokenToken,
}: {
c: codes.PermissionDenied,
m: msgBrokenToken,
},
// Missing object ID in token
{
t: object.RequestPut,
e: errWrongTokenAddress,
}: {
c: codes.PermissionDenied,
m: msgTokenObjectID,
d: tokenOIDDetails(),
},
// Invalid after-first message in stream
{
t: object.RequestPut,
e: errChunkExpected,
}: {
c: codes.InvalidArgument,
m: msgPutMessageProblem,
d: putChunkMessageDetails(),
},
{
t: object.RequestPut,
e: errObjectFromTheFuture,
}: {
c: codes.FailedPrecondition,
m: msgObjectCreationEpoch,
d: nil, // TODO: NSPCC-1048
},
{
t: object.RequestPut,
e: errObjectPayloadSize,
}: {
c: codes.FailedPrecondition,
m: msgObjectPayloadSize,
d: nil, // TODO: NSPCC-1048
},
{
t: object.RequestPut,
e: errLocalStorageOverflow,
}: {
c: codes.Unavailable,
m: msgLocalStorageOverflow,
d: localStorageOverflowDetails(),
},
{
t: object.RequestPut,
e: errPayloadChecksum,
}: {
c: codes.InvalidArgument,
m: msgPayloadChecksum,
d: payloadChecksumHeaderDetails(),
},
{
t: object.RequestPut,
e: errObjectHeadersVerification,
}: {
c: codes.InvalidArgument,
m: msgObjectHeadersVerification,
},
{
t: object.RequestPut,
e: errIncompleteOperation,
}: {
c: codes.Unavailable,
m: msgForwardPutObject,
},
{
t: object.RequestPut,
e: errPutLocal,
}: {
c: codes.Internal,
m: msgPutLocalFailure,
},
{
t: object.RequestPut,
e: errTokenRetrieval,
}: {
c: codes.Aborted,
m: msgPrivateTokenRecv,
},
{
t: object.RequestPut,
e: transformer.ErrInvalidSGLinking,
}: {
c: codes.InvalidArgument,
m: msgInvalidSGLinking,
d: sgLinkingDetails(),
},
{
t: object.RequestPut,
e: implementations.ErrIncompleteSGInfo,
}: {
c: codes.NotFound,
m: msgIncompleteSGInfo,
},
{
t: object.RequestPut,
e: errTransformer,
}: {
c: codes.Internal,
m: msgTransformationFailure,
},
{
t: object.RequestPut,
e: errWrongSGSize,
}: {
c: codes.InvalidArgument,
m: msgWrongSGSize,
},
{
t: object.RequestPut,
e: errWrongSGHash,
}: {
c: codes.InvalidArgument,
m: msgWrongSGHash,
},
{
t: object.RequestGet,
e: errIncompleteOperation,
}: {
c: codes.NotFound,
m: msgObjectNotFound,
},
{
t: object.RequestHead,
e: errIncompleteOperation,
}: {
c: codes.NotFound,
m: msgObjectHeaderNotFound,
},
{
t: object.RequestGet,
e: errNonAssembly,
}: {
c: codes.Unimplemented,
m: msgNonAssembly,
},
{
t: object.RequestHead,
e: errNonAssembly,
}: {
c: codes.Unimplemented,
m: msgNonAssembly,
},
{
t: object.RequestGet,
e: childrenNotFound,
}: {
c: codes.NotFound,
m: msgObjectNotFound,
},
{
t: object.RequestHead,
e: childrenNotFound,
}: {
c: codes.NotFound,
m: msgObjectHeaderNotFound,
},
{
t: object.RequestRange,
e: localstore.ErrOutOfRange,
}: {
c: codes.OutOfRange,
m: msgPayloadOutOfRange,
},
{
t: object.RequestRange,
e: errPayloadRangeNotFound,
}: {
c: codes.NotFound,
m: msgPayloadRangeNotFound,
},
{
t: object.RequestDelete,
e: errNilToken,
}: {
c: codes.InvalidArgument,
m: msgMissingToken,
d: missingTokenDetails(),
},
{
t: object.RequestDelete,
e: errMissingOwnerKeys,
}: {
c: codes.PermissionDenied,
m: msgMissingTokenKeys,
d: tokenKeysDetails(),
},
{
t: object.RequestDelete,
e: errBrokenToken,
}: {
c: codes.PermissionDenied,
m: msgBrokenToken,
},
{
t: object.RequestDelete,
e: errWrongTokenAddress,
}: {
c: codes.PermissionDenied,
m: msgTokenObjectID,
d: tokenOIDDetails(),
},
{
t: object.RequestDelete,
e: errTokenRetrieval,
}: {
c: codes.Aborted,
m: msgPrivateTokenRecv,
},
{
t: object.RequestDelete,
e: errIncompleteOperation,
}: {
c: codes.Unavailable,
m: msgPutTombstone,
},
{
t: object.RequestDelete,
e: errDeletePrepare,
}: {
c: codes.Internal,
m: msgDeletePrepare,
},
{
t: object.RequestSearch,
e: errUnsupportedQueryVersion,
}: {
c: codes.Unimplemented,
m: msgQueryVersion,
},
{
t: object.RequestSearch,
e: errSearchQueryUnmarshal,
}: {
c: codes.InvalidArgument,
m: msgSearchQueryUnmarshal,
},
{
t: object.RequestSearch,
e: errLocalQueryImpose,
}: {
c: codes.Internal,
m: msgLocalQueryImpose,
},
{
t: object.RequestRangeHash,
e: errPayloadRangeNotFound,
}: {
c: codes.NotFound,
m: msgPayloadRangeNotFound,
},
{
t: object.RequestRangeHash,
e: localstore.ErrOutOfRange,
}: {
c: codes.OutOfRange,
m: msgPayloadOutOfRange,
},
}
func serviceStatusCalculator() *statusCalculator {
s := newStatusCalculator()
for k, v := range mStatusCommon {
s.addCommon(k, v)
}
for k, v := range mStatusCustom {
s.addCustom(k, v)
}
return s
}
func statusError(v *statusInfo) (bool, error) {
st, err := status.New(v.c, v.m).WithDetails(v.d...)
if err != nil {
return false, nil
}
return true, st.Err()
}
func (s *statusCalculator) addCommon(k error, v *statusInfo) {
s.Lock()
s.common[k] = v
s.Unlock()
}
func (s *statusCalculator) addCustom(k requestError, v *statusInfo) {
s.Lock()
s.custom[k] = v
s.Unlock()
}
func (s *statusCalculator) make(e requestError) error {
s.RLock()
defer s.RUnlock()
var (
ok bool
v *statusInfo
d []proto.Message
err = errors.Cause(e.e)
)
if v, ok := err.(*detailedError); ok {
d = v.d
err = v.error
} else if v, ok := err.(detailedError); ok {
d = v.d
err = v.error
}
if v, ok = s.common[err]; !ok {
if v, ok = s.custom[requestError{
t: e.t,
e: err,
}]; !ok {
return e.e
}
}
vv := *v
vv.d = append(vv.d, d...)
if ok, res := statusError(&vv); ok {
return res
}
return e.e
}
func newStatusCalculator() *statusCalculator {
return &statusCalculator{
RWMutex: new(sync.RWMutex),
common: make(map[error]*statusInfo),
custom: make(map[requestError]*statusInfo),
}
}
func requestAuthDetails() []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "Signatures",
Description: "should be formed according to VerificationHeader signing",
},
},
},
}
}
func invalidTTLDetails() []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "TTL",
Description: "should greater or equal than NonForwardingTTL",
},
},
},
}
}
func containerAbsenceDetails() []proto.Message {
return []proto.Message{
&errdetails.PreconditionFailure{
Violations: []*errdetails.PreconditionFailure_Violation{
{
Type: "container options",
Subject: "container nodes",
Description: "server node should be presented container",
},
},
},
}
}
func containerDetails(cid CID, desc string) []proto.Message {
return []proto.Message{
&errdetails.ResourceInfo{
ResourceType: "container",
ResourceName: cid.String(),
Description: desc,
},
}
}
func putFirstMessageDetails() []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "R",
Description: "should be PutRequest_Header",
},
},
},
}
}
func putChunkMessageDetails() []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "R",
Description: "should be PutRequest_Chunk",
},
{
Field: "R.Chunk",
Description: "should not be empty",
},
},
},
}
}
func putNilObjectDetails() []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "R.Object",
Description: "should not be null",
},
},
},
}
}
func payloadSizeDetails() []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "R.Object.SystemHeader.PayloadLength",
Description: "should be equal to the sum of the sizes of the streaming payload chunks",
},
},
},
}
}
func tokenKeysDetails() []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "R.Token.PublicKeys",
Description: "should be non-empty list of marshaled ecdsa public keys",
},
},
},
}
}
func tokenOIDDetails() []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "R.Token.ObjectID",
Description: "should contain requested object",
},
},
},
}
}
func maxProcPayloadSizeDetails(sz uint64) []proto.Message {
return []proto.Message{
&errdetails.PreconditionFailure{
Violations: []*errdetails.PreconditionFailure_Violation{
{
Type: "object requirements",
Subject: "max processing payload size",
Description: fmt.Sprintf("should not be greater than %d bytes", sz),
},
},
},
}
}
func objectCreationEpochDetails(e uint64) []proto.Message {
return []proto.Message{
&errdetails.PreconditionFailure{
Violations: []*errdetails.PreconditionFailure_Violation{
{
Type: "object requirements",
Subject: "creation epoch",
Description: fmt.Sprintf("should not be greater than %d", e),
},
},
},
}
}
func maxObjectPayloadSizeDetails(sz uint64) []proto.Message {
return []proto.Message{
&errdetails.PreconditionFailure{
Violations: []*errdetails.PreconditionFailure_Violation{
{
Type: "object requirements",
Subject: "max object payload size",
Description: fmt.Sprintf("should not be greater than %d bytes", sz),
},
},
},
}
}
func localStorageOverflowDetails() []proto.Message {
return []proto.Message{
&errdetails.ResourceInfo{
ResourceType: "local storage",
ResourceName: "disk storage",
Description: "not enough space",
},
}
}
func payloadChecksumHeaderDetails() []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "R.Object.Headers",
Description: "should contain correct payload checksum header",
},
},
},
}
}
func objectHeadersVerificationDetails(e error) []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "R.Object.Headers",
Description: e.Error(),
},
},
},
}
}
func privateTokenRecvDetails(id session.TokenID, owner OwnerID) []proto.Message {
return []proto.Message{
&errdetails.ResourceInfo{
ResourceType: "private token",
ResourceName: id.String(),
Owner: owner.String(),
Description: "problems with getting a private token",
},
}
}
func sgLinkingDetails() []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "R.Object.Headers",
Description: "should not contain Header_StorageGroup and Link_StorageGroup or should contain both",
},
},
},
}
}
func sgSizeDetails(exp, act uint64) []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "R.Object.Headers",
Description: fmt.Sprintf("wrong storage group size: expected %d, collected %d", exp, act),
},
},
},
}
}
func sgHashDetails(exp, act Hash) []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "R.Object.Headers",
Description: fmt.Sprintf("wrong storage group hash: expected %s, collected %s", exp, act),
},
},
},
}
}
func missingTokenDetails() []proto.Message {
return []proto.Message{
&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Field: "Token",
Description: "should not be null",
},
},
},
}
}