frostfs-node/pkg/network/transport/object/grpc/status.go

903 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"
2020-07-24 13:54:03 +00:00
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/localstore"
"github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer"
"github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transport/storagegroup"
"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"
2020-07-24 13:54:03 +00:00
const msgServerPanic = "panic occurred during request processing"
2020-07-24 13:54:03 +00:00
var errServerPanic = errors.New("panic on call handler")
2020-07-24 13:54:03 +00:00
const msgUnauthenticated = "request does not have valid authentication credentials for the operation"
2020-07-24 13:54:03 +00:00
var errUnauthenticated = errors.New("unauthenticated request")
const msgReSigning = "server could not re-sign request"
var errReSigning = errors.New("could not re-sign request")
const msgInvalidTTL = "invalid TTL value"
var errInvalidTTL = errors.New("invalid TTL value")
const (
msgNotLocalContainer = "server is not presented in container"
descNotLocalContainer = "server is outside container"
)
2020-07-24 13:54:03 +00:00
var errNotLocalContainer = errors.New("not local container")
const msgContainerAffiliationProblem = "server could not check container affiliation"
var errContainerAffiliationProblem = errors.New("could not check container affiliation")
const (
msgContainerNotFound = "container not found"
descContainerNotFound = "handling a non-existent container"
)
2020-07-24 13:54:03 +00:00
var errContainerNotFound = errors.New("container not found")
2020-07-24 13:54:03 +00:00
const msgPlacementProblem = "there were problems building the placement vector on the server"
2020-07-24 13:54:03 +00:00
var errPlacementProblem = errors.New("could not traverse over container")
2020-07-24 13:54:03 +00:00
const msgOverloaded = "system resource overloaded"
2020-07-24 13:54:03 +00:00
var errOverloaded = errors.New("system resource overloaded")
const msgAccessDenied = "access to requested operation is denied"
var errAccessDenied = errors.New("access denied")
const msgPutMessageProblem = "invalid message type"
var 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"
)
2020-07-24 13:54:03 +00:00
const msgProcPayloadSize = "max payload size of processing object overflow"
2020-07-24 13:54:03 +00:00
var errProcPayloadSize = errors.New("max processing object payload size overflow")
2020-07-24 13:54:03 +00:00
const msgObjectCreationEpoch = "invalid creation epoch of object"
2020-07-24 13:54:03 +00:00
var errObjectFromTheFuture = errors.New("object from the future")
2020-07-24 13:54:03 +00:00
const msgObjectPayloadSize = "max object payload size overflow"
2020-07-24 13:54:03 +00:00
var errObjectPayloadSize = errors.New("max object payload size overflow")
2020-07-24 13:54:03 +00:00
const msgLocalStorageOverflow = "not enough space in local storage"
2020-07-24 13:54:03 +00:00
var errLocalStorageOverflow = errors.New("local storage overflow")
2020-07-24 13:54:03 +00:00
const msgPayloadChecksum = "invalid payload checksum"
2020-07-24 13:54:03 +00:00
var errPayloadChecksum = errors.New("invalid payload checksum")
2020-07-24 13:54:03 +00:00
const msgObjectHeadersVerification = "object headers failed verification"
2020-07-24 13:54:03 +00:00
var errObjectHeadersVerification = errors.New("object headers failed verification")
2020-07-24 13:54:03 +00:00
const msgForwardPutObject = "forward object failure"
2020-07-24 13:54:03 +00:00
const msgPutLocalFailure = "local object put failure"
2020-07-24 13:54:03 +00:00
var errPutLocal = errors.New("local object put failure")
2020-07-24 13:54:03 +00:00
const msgPrivateTokenRecv = "private token receive failure"
2020-07-24 13:54:03 +00:00
const msgInvalidSGLinking = "invalid storage group headers"
2020-07-24 13:54:03 +00:00
const msgIncompleteSGInfo = "collect storage group info failure"
2020-07-24 13:54:03 +00:00
const msgTransformationFailure = "object preparation failure"
2020-07-24 13:54:03 +00:00
const msgWrongSGSize = "wrong storage group size"
2020-07-24 13:54:03 +00:00
var errWrongSGSize = errors.New("wrong storage group size")
2020-07-24 13:54:03 +00:00
const msgWrongSGHash = "wrong storage group homomorphic hash"
2020-07-24 13:54:03 +00:00
var errWrongSGHash = errors.New("wrong storage group homomorphic hash")
2020-07-24 13:54:03 +00:00
const msgObjectNotFound = "object not found"
2020-07-24 13:54:03 +00:00
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"
var errPayloadRangeNotFound = errors.New("object payload range not found")
const msgMissingToken = "missing token in request"
const msgPutTombstone = "could not store tombstone"
const msgDeletePrepare = "delete information preparation failure"
var errDeletePrepare = errors.New("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,
2020-07-24 13:54:03 +00:00
e: storagegroup.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",
},
},
},
}
}