From c6bc8c513b134de0354541a3c8db98b19a7ab19b Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Wed, 22 Jul 2020 16:02:32 +0300 Subject: [PATCH] NFSSVC-30 Isolate S3 routing from legacy code --- auth/errors.go | 17 + cmd/gate/app-new-auth.go | 2 + cmd/gate/app-settings.go | 16 +- cmd/gate/app.go | 52 +- neofs/api/crypto/errors.go | 63 + neofs/api/errors.go | 1964 ++++++++++++++++++++++++++++ neofs/api/handler/unimplemented.go | 501 +++++++ neofs/api/max-clients.go | 56 + neofs/api/object-errors.go | 474 +++++++ neofs/api/reqinfo.go | 112 ++ neofs/api/response.go | 130 ++ neofs/api/router.go | 301 +++++ neofs/api/storage-errors.go | 93 ++ neofs/api/typed-errors.go | 107 ++ neofs/api/xl-v1-errors.go | 12 + 15 files changed, 3877 insertions(+), 23 deletions(-) create mode 100644 auth/errors.go create mode 100644 neofs/api/crypto/errors.go create mode 100644 neofs/api/errors.go create mode 100644 neofs/api/handler/unimplemented.go create mode 100644 neofs/api/max-clients.go create mode 100644 neofs/api/object-errors.go create mode 100644 neofs/api/reqinfo.go create mode 100644 neofs/api/response.go create mode 100644 neofs/api/router.go create mode 100644 neofs/api/storage-errors.go create mode 100644 neofs/api/typed-errors.go create mode 100644 neofs/api/xl-v1-errors.go diff --git a/auth/errors.go b/auth/errors.go new file mode 100644 index 00000000..4a0dab58 --- /dev/null +++ b/auth/errors.go @@ -0,0 +1,17 @@ +package auth + +import "fmt" + +const ( + // Minimum length for MinIO access key. + accessKeyMinLen = 3 + + // Minimum length for MinIO secret key for both server and gateway mode. + secretKeyMinLen = 8 +) + +// Common errors generated for access and secret key validation. +var ( + ErrInvalidAccessKeyLength = fmt.Errorf("access key must be minimum %v or more characters long", accessKeyMinLen) + ErrInvalidSecretKeyLength = fmt.Errorf("secret key must be minimum %v or more characters long", secretKeyMinLen) +) diff --git a/cmd/gate/app-new-auth.go b/cmd/gate/app-new-auth.go index 6410193e..9c26a2f5 100644 --- a/cmd/gate/app-new-auth.go +++ b/cmd/gate/app-new-auth.go @@ -20,5 +20,7 @@ func attachNewUserAuth(router *mux.Router, center *auth.Center, log *zap.Logger) }) } + // TODO: should not be used for all routes, + // only for API router.Use(uamw) } diff --git a/cmd/gate/app-settings.go b/cmd/gate/app-settings.go index 323338f7..0224d863 100644 --- a/cmd/gate/app-settings.go +++ b/cmd/gate/app-settings.go @@ -39,6 +39,9 @@ const ( defaultKeepaliveTime = 10 * time.Second defaultKeepaliveTimeout = 10 * time.Second + + defaultMaxClientsCount = 100 + defaultMaxClientsDeadline = time.Second * 30 ) const ( // settings @@ -69,6 +72,10 @@ const ( // settings cfgRequestTimeout = "request_timeout" cfgRebalanceTimer = "rebalance_timer" + // MaxClients + cfgMaxClientsCount = "max_clients_count" + cfgMaxClientsDeadline = "max_clients_deadline" + // gRPC cfgGRPCVerbose = "verbose" @@ -108,14 +115,16 @@ func fetchAuthCenter(l *zap.Logger, v *viper.Viper) (*auth.Center, error) { uapk := v.GetString(cfgUserAuthPrivateKey) userAuthPrivateKey, err = auth.ReadRSAPrivateKeyFromPEMFile(uapk) if err != nil { - return nil, errors.Wrap(err, "could not load UserAuth private key") + return nil, errors.Wrapf(err, "could not load UserAuth private key %q", uapk) } center, err := auth.NewCenter(l) if err != nil { return nil, errors.Wrap(err, "failed to create auth center") } center.SetUserAuthKeys(userAuthPrivateKey) - center.SetNeoFSKeys(neofsPrivateKey) + if err = center.SetNeoFSKeys(neofsPrivateKey); err != nil { + return nil, err + } return center, nil } @@ -168,6 +177,9 @@ func newSettings() *viper.Viper { flags.Duration(cfgConnectTimeout, defaultConnectTimeout, "set gRPC connect timeout") flags.Duration(cfgRebalanceTimer, defaultRebalanceTimer, "set gRPC connection rebalance timer") + flags.Int(cfgMaxClientsCount, defaultMaxClientsCount, "set max-clients count") + flags.Duration(cfgMaxClientsDeadline, defaultMaxClientsDeadline, "set max-clients deadline") + ttl := flags.DurationP(cfgConnectionTTL, "t", defaultTTL, "set gRPC connection time to live") flags.String(cfgListenAddress, "0.0.0.0:8080", "set address to listen") diff --git a/cmd/gate/app.go b/cmd/gate/app.go index f7c7c1c2..263e336f 100644 --- a/cmd/gate/app.go +++ b/cmd/gate/app.go @@ -8,14 +8,17 @@ import ( "time" "github.com/minio/minio/auth" - minio "github.com/minio/minio/legacy" - "github.com/minio/minio/legacy/config" + "github.com/minio/minio/neofs/api" + "github.com/minio/minio/neofs/api/handler" "github.com/minio/minio/neofs/layer" - "github.com/minio/minio/neofs/metrics" "github.com/minio/minio/neofs/pool" "github.com/spf13/viper" "go.uber.org/zap" "google.golang.org/grpc/keepalive" + + // should be removed in future + "github.com/minio/minio/legacy" + "github.com/minio/minio/legacy/config" ) type ( @@ -25,13 +28,16 @@ type ( log *zap.Logger cfg *viper.Viper tls *tlsConfig - obj minio.ObjectLayer + obj legacy.ObjectLayer + api api.Handler conTimeout time.Duration reqTimeout time.Duration reBalance time.Duration + maxClients api.MaxClients + webDone chan struct{} wrkDone chan struct{} } @@ -47,10 +53,14 @@ func newApp(l *zap.Logger, v *viper.Viper) *App { err error cli pool.Pool tls *tlsConfig - obj minio.ObjectLayer + caller api.Handler + obj legacy.ObjectLayer reBalance = defaultRebalanceTimer conTimeout = defaultConnectTimeout reqTimeout = defaultRequestTimeout + + maxClientsCount = defaultMaxClientsCount + maxClientsDeadline = defaultMaxClientsDeadline ) center, err := fetchAuthCenter(l, v) @@ -60,6 +70,10 @@ func newApp(l *zap.Logger, v *viper.Viper) *App { uid := center.GetOwnerID() wif := center.GetWIFString() + if caller, err = handler.New(); err != nil { + l.Fatal("could not initialize API handler", zap.Error(err)) + } + if v.IsSet(cfgTLSKeyFile) && v.IsSet(cfgTLSCertFile) { tls = &tlsConfig{ KeyFile: v.GetString(cfgTLSKeyFile), @@ -75,6 +89,14 @@ func newApp(l *zap.Logger, v *viper.Viper) *App { reqTimeout = v } + if v := v.GetInt(cfgMaxClientsCount); v > 0 { + maxClientsCount = v + } + + if v := v.GetDuration(cfgMaxClientsDeadline); v > 0 { + maxClientsDeadline = v + } + poolConfig := &pool.Config{ ConnectionTTL: v.GetDuration(cfgConnectionTTL), ConnectTimeout: v.GetDuration(cfgConnectTimeout), @@ -133,12 +155,15 @@ func newApp(l *zap.Logger, v *viper.Viper) *App { cfg: v, obj: obj, tls: tls, + api: caller, webDone: make(chan struct{}, 1), wrkDone: make(chan struct{}, 1), reBalance: reBalance, + maxClients: api.NewMaxClientsMiddleware(maxClientsCount, maxClientsDeadline), + conTimeout: conTimeout, reqTimeout: reqTimeout, } @@ -179,23 +204,8 @@ func (a *App) Server(ctx context.Context) { attachMetrics(router, a.cfg, a.log) attachProfiler(router, a.cfg, a.log) - { // Example for metrics.Middleware and metrics.APIStats - r := router.PathPrefix("/test-metrics").Subrouter() - r.Handle("/foo", metrics.APIStats("foo", func(w http.ResponseWriter, r *http.Request) { - // do something - })) - - m := r.PathPrefix("/bar").Subrouter() - m.Use(metrics.Middleware) - m.Handle("", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // do something - })) - } - // Attach S3 API: - r := router.PathPrefix(minio.SlashSeparator).Subrouter() - r.Use(metrics.Middleware) - minio.AttachS3API(r, a.obj, a.log) + api.Attach(router, a.maxClients, a.api) // Use mux.Router as http.Handler srv.Handler = router diff --git a/neofs/api/crypto/errors.go b/neofs/api/crypto/errors.go new file mode 100644 index 00000000..14beb9c1 --- /dev/null +++ b/neofs/api/crypto/errors.go @@ -0,0 +1,63 @@ +package crypto + +import "fmt" + +// Error is the generic type for any error happening during decrypting +// an object. It indicates that the object itself or its metadata was +// modified accidentally or maliciously. +type Error struct { + err error +} + +// Errorf - formats according to a format specifier and returns +// the string as a value that satisfies error of type crypto.Error +func Errorf(format string, a ...interface{}) error { + return Error{err: fmt.Errorf(format, a...)} +} + +// Unwrap the internal error. +func (e Error) Unwrap() error { return e.err } + +// Error 'error' compatible method. +func (e Error) Error() string { + if e.err == nil { + return "crypto: cause " + } + return e.err.Error() +} + +var ( + // ErrInvalidEncryptionMethod indicates that the specified SSE encryption method + // is not supported. + ErrInvalidEncryptionMethod = Errorf("The encryption method is not supported") + + // ErrInvalidCustomerAlgorithm indicates that the specified SSE-C algorithm + // is not supported. + ErrInvalidCustomerAlgorithm = Errorf("The SSE-C algorithm is not supported") + + // ErrMissingCustomerKey indicates that the HTTP headers contains no SSE-C client key. + ErrMissingCustomerKey = Errorf("The SSE-C request is missing the customer key") + + // ErrMissingCustomerKeyMD5 indicates that the HTTP headers contains no SSE-C client key + // MD5 checksum. + ErrMissingCustomerKeyMD5 = Errorf("The SSE-C request is missing the customer key MD5") + + // ErrInvalidCustomerKey indicates that the SSE-C client key is not valid - e.g. not a + // base64-encoded string or not 256 bits long. + ErrInvalidCustomerKey = Errorf("The SSE-C client key is invalid") + + // ErrSecretKeyMismatch indicates that the provided secret key (SSE-C client key / SSE-S3 KMS key) + // does not match the secret key used during encrypting the object. + ErrSecretKeyMismatch = Errorf("The secret key does not match the secret key used during upload") + + // ErrCustomerKeyMD5Mismatch indicates that the SSE-C key MD5 does not match the + // computed MD5 sum. This means that the client provided either the wrong key for + // a certain MD5 checksum or the wrong MD5 for a certain key. + ErrCustomerKeyMD5Mismatch = Errorf("The provided SSE-C key MD5 does not match the computed MD5 of the SSE-C key") + // ErrIncompatibleEncryptionMethod indicates that both SSE-C headers and SSE-S3 headers were specified, and are incompatible + // The client needs to remove the SSE-S3 header or the SSE-C headers + ErrIncompatibleEncryptionMethod = Errorf("Server side encryption specified with both SSE-C and SSE-S3 headers") + + // ErrKMSAuthLogin is raised when there is a failure authenticating to KMS + ErrKMSAuthLogin = Errorf("Vault service did not return auth info") +) diff --git a/neofs/api/errors.go b/neofs/api/errors.go new file mode 100644 index 00000000..c56b03b1 --- /dev/null +++ b/neofs/api/errors.go @@ -0,0 +1,1964 @@ +package api + +import ( + "context" + "encoding/xml" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/Azure/azure-storage-blob-go/azblob" + "github.com/minio/minio-go/v6" + "github.com/minio/minio-go/v6/pkg/tags" + "github.com/minio/minio/auth" + "github.com/minio/minio/neofs/api/crypto" + "github.com/minio/minio/pkg/bucket/lifecycle" + objectlock "github.com/minio/minio/pkg/bucket/object/lock" + "github.com/minio/minio/pkg/bucket/policy" + "github.com/minio/minio/pkg/event" + "github.com/minio/minio/pkg/hash" + "google.golang.org/api/googleapi" +) + +type ( + // ErrorCode type of error status. + ErrorCode int + + errorCodeMap map[ErrorCode]Error +) + +const maxEConfigJSONSize = 262272 + +// Error codes, non exhaustive list - http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html +const ( + ErrNone ErrorCode = iota + ErrAccessDenied + ErrBadDigest + ErrEntityTooSmall + ErrEntityTooLarge + ErrPolicyTooLarge + ErrIncompleteBody + ErrInternalError + ErrInvalidAccessKeyID + ErrInvalidBucketName + ErrInvalidDigest + ErrInvalidRange + ErrInvalidCopyPartRange + ErrInvalidCopyPartRangeSource + ErrInvalidMaxKeys + ErrInvalidEncodingMethod + ErrInvalidMaxUploads + ErrInvalidMaxParts + ErrInvalidPartNumberMarker + ErrInvalidRequestBody + ErrInvalidCopySource + ErrInvalidMetadataDirective + ErrInvalidCopyDest + ErrInvalidPolicyDocument + ErrInvalidObjectState + ErrMalformedXML + ErrMissingContentLength + ErrMissingContentMD5 + ErrMissingRequestBodyError + ErrMissingSecurityHeader + ErrNoSuchBucket + ErrNoSuchBucketPolicy + ErrNoSuchBucketLifecycle + ErrNoSuchLifecycleConfiguration + ErrNoSuchBucketSSEConfig + ErrNoSuchCORSConfiguration + ErrNoSuchWebsiteConfiguration + ErrReplicationConfigurationNotFoundError + ErrNoSuchKey + ErrNoSuchUpload + ErrNoSuchVersion + ErrNotImplemented + ErrPreconditionFailed + ErrRequestTimeTooSkewed + ErrSignatureDoesNotMatch + ErrMethodNotAllowed + ErrInvalidPart + ErrInvalidPartOrder + ErrAuthorizationHeaderMalformed + ErrMalformedPOSTRequest + ErrPOSTFileRequired + ErrSignatureVersionNotSupported + ErrBucketNotEmpty + ErrAllAccessDisabled + ErrMalformedPolicy + ErrMissingFields + ErrMissingCredTag + ErrCredMalformed + ErrInvalidRegion + ErrInvalidServiceS3 + ErrInvalidServiceSTS + ErrInvalidRequestVersion + ErrMissingSignTag + ErrMissingSignHeadersTag + ErrMalformedDate + ErrMalformedPresignedDate + ErrMalformedCredentialDate + ErrMalformedCredentialRegion + ErrMalformedExpires + ErrNegativeExpires + ErrAuthHeaderEmpty + ErrExpiredPresignRequest + ErrRequestNotReadyYet + ErrUnsignedHeaders + ErrMissingDateHeader + ErrInvalidQuerySignatureAlgo + ErrInvalidQueryParams + ErrBucketAlreadyOwnedByYou + ErrInvalidDuration + ErrBucketAlreadyExists + ErrMetadataTooLarge + ErrUnsupportedMetadata + ErrMaximumExpires + ErrSlowDown + ErrInvalidPrefixMarker + ErrBadRequest + ErrKeyTooLongError + ErrInvalidBucketObjectLockConfiguration + ErrObjectLockConfigurationNotFound + ErrObjectLockConfigurationNotAllowed + ErrNoSuchObjectLockConfiguration + ErrObjectLocked + ErrInvalidRetentionDate + ErrPastObjectLockRetainDate + ErrUnknownWORMModeDirective + ErrBucketTaggingNotFound + ErrObjectLockInvalidHeaders + ErrInvalidTagDirective + // Add new error codes here. + + // SSE-S3 related API errors + ErrInvalidEncryptionMethod + + // Server-Side-Encryption (with Customer provided key) related API errors. + ErrInsecureSSECustomerRequest + ErrSSEMultipartEncrypted + ErrSSEEncryptedObject + ErrInvalidEncryptionParameters + ErrInvalidSSECustomerAlgorithm + ErrInvalidSSECustomerKey + ErrMissingSSECustomerKey + ErrMissingSSECustomerKeyMD5 + ErrSSECustomerKeyMD5Mismatch + ErrInvalidSSECustomerParameters + ErrIncompatibleEncryptionMethod + ErrKMSNotConfigured + ErrKMSAuthFailure + + ErrNoAccessKey + ErrInvalidToken + + // Bucket notification related errors. + ErrEventNotification + ErrARNNotification + ErrRegionNotification + ErrOverlappingFilterNotification + ErrFilterNameInvalid + ErrFilterNamePrefix + ErrFilterNameSuffix + ErrFilterValueInvalid + ErrOverlappingConfigs + ErrUnsupportedNotification + + // S3 extended errors. + ErrContentSHA256Mismatch + + // Add new extended error codes here. + + // MinIO extended errors. + // ErrReadQuorum + // ErrWriteQuorum + ErrParentIsObject + ErrStorageFull + ErrRequestBodyParse + ErrObjectExistsAsDirectory + ErrInvalidObjectName + ErrInvalidObjectNamePrefixSlash + ErrInvalidResourceName + ErrServerNotInitialized + ErrOperationTimedOut + ErrOperationMaxedOut + ErrInvalidRequest + // MinIO storage class error codes + ErrInvalidStorageClass + ErrBackendDown + // Add new extended error codes here. + // Please open a https://github.com/minio/minio/issues before adding + // new error codes here. + + ErrMalformedJSON + ErrAdminNoSuchUser + ErrAdminNoSuchGroup + ErrAdminGroupNotEmpty + ErrAdminNoSuchPolicy + ErrAdminInvalidArgument + ErrAdminInvalidAccessKey + ErrAdminInvalidSecretKey + ErrAdminConfigNoQuorum + ErrAdminConfigTooLarge + ErrAdminConfigBadJSON + ErrAdminConfigDuplicateKeys + ErrAdminCredentialsMismatch + ErrInsecureClientRequest + ErrObjectTampered + // Bucket Quota error codes + ErrAdminBucketQuotaExceeded + ErrAdminNoSuchQuotaConfiguration + ErrAdminBucketQuotaDisabled + + ErrHealNotImplemented + ErrHealNoSuchProcess + ErrHealInvalidClientToken + ErrHealMissingBucket + ErrHealAlreadyRunning + ErrHealOverlappingPaths + ErrIncorrectContinuationToken + + // S3 Select Errors + ErrEmptyRequestBody + ErrUnsupportedFunction + ErrInvalidExpressionType + ErrBusy + ErrUnauthorizedAccess + ErrExpressionTooLong + ErrIllegalSQLFunctionArgument + ErrInvalidKeyPath + ErrInvalidCompressionFormat + ErrInvalidFileHeaderInfo + ErrInvalidJSONType + ErrInvalidQuoteFields + ErrInvalidRequestParameter + ErrInvalidDataType + ErrInvalidTextEncoding + ErrInvalidDataSource + ErrInvalidTableAlias + ErrMissingRequiredParameter + ErrObjectSerializationConflict + ErrUnsupportedSQLOperation + ErrUnsupportedSQLStructure + ErrUnsupportedSyntax + ErrUnsupportedRangeHeader + ErrLexerInvalidChar + ErrLexerInvalidOperator + ErrLexerInvalidLiteral + ErrLexerInvalidIONLiteral + ErrParseExpectedDatePart + ErrParseExpectedKeyword + ErrParseExpectedTokenType + ErrParseExpected2TokenTypes + ErrParseExpectedNumber + ErrParseExpectedRightParenBuiltinFunctionCall + ErrParseExpectedTypeName + ErrParseExpectedWhenClause + ErrParseUnsupportedToken + ErrParseUnsupportedLiteralsGroupBy + ErrParseExpectedMember + ErrParseUnsupportedSelect + ErrParseUnsupportedCase + ErrParseUnsupportedCaseClause + ErrParseUnsupportedAlias + ErrParseUnsupportedSyntax + ErrParseUnknownOperator + ErrParseMissingIdentAfterAt + ErrParseUnexpectedOperator + ErrParseUnexpectedTerm + ErrParseUnexpectedToken + ErrParseUnexpectedKeyword + ErrParseExpectedExpression + ErrParseExpectedLeftParenAfterCast + ErrParseExpectedLeftParenValueConstructor + ErrParseExpectedLeftParenBuiltinFunctionCall + ErrParseExpectedArgumentDelimiter + ErrParseCastArity + ErrParseInvalidTypeParam + ErrParseEmptySelect + ErrParseSelectMissingFrom + ErrParseExpectedIdentForGroupName + ErrParseExpectedIdentForAlias + ErrParseUnsupportedCallWithStar + ErrParseNonUnaryAgregateFunctionCall + ErrParseMalformedJoin + ErrParseExpectedIdentForAt + ErrParseAsteriskIsNotAloneInSelectList + ErrParseCannotMixSqbAndWildcardInSelectList + ErrParseInvalidContextForWildcardInSelectList + ErrIncorrectSQLFunctionArgumentType + ErrValueParseFailure + ErrEvaluatorInvalidArguments + ErrIntegerOverflow + ErrLikeInvalidInputs + ErrCastFailed + ErrInvalidCast + ErrEvaluatorInvalidTimestampFormatPattern + ErrEvaluatorInvalidTimestampFormatPatternSymbolForParsing + ErrEvaluatorTimestampFormatPatternDuplicateFields + ErrEvaluatorTimestampFormatPatternHourClockAmPmMismatch + ErrEvaluatorUnterminatedTimestampFormatPatternToken + ErrEvaluatorInvalidTimestampFormatPatternToken + ErrEvaluatorInvalidTimestampFormatPatternSymbol + ErrEvaluatorBindingDoesNotExist + ErrMissingHeaders + ErrInvalidColumnIndex + + ErrAdminConfigNotificationTargetsFailed + ErrAdminProfilerNotEnabled + ErrInvalidDecompressedSize + ErrAddUserInvalidArgument + ErrAdminAccountNotEligible + ErrServiceAccountNotFound + ErrPostPolicyConditionInvalidFormat +) + +// error code to Error structure, these fields carry respective +// descriptions for all the error responses. +var errorCodes = errorCodeMap{ + ErrInvalidCopyDest: { + Code: "InvalidRequest", + Description: "This copy request is illegal because it is trying to copy an object to itself without changing the object's metadata, storage class, website redirect location or encryption attributes.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidCopySource: { + Code: "InvalidArgument", + Description: "Copy Source must mention the source bucket and key: sourcebucket/sourcekey.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidMetadataDirective: { + Code: "InvalidArgument", + Description: "Unknown metadata directive.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidStorageClass: { + Code: "InvalidStorageClass", + Description: "Invalid storage class.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidRequestBody: { + Code: "InvalidArgument", + Description: "Body shouldn't be set for this request.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidMaxUploads: { + Code: "InvalidArgument", + Description: "Argument max-uploads must be an integer between 0 and 2147483647", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidMaxKeys: { + Code: "InvalidArgument", + Description: "Argument maxKeys must be an integer between 0 and 2147483647", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidEncodingMethod: { + Code: "InvalidArgument", + Description: "Invalid Encoding Method specified in Request", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidMaxParts: { + Code: "InvalidArgument", + Description: "Argument max-parts must be an integer between 0 and 2147483647", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidPartNumberMarker: { + Code: "InvalidArgument", + Description: "Argument partNumberMarker must be an integer.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidPolicyDocument: { + Code: "InvalidPolicyDocument", + Description: "The content of the form does not meet the conditions specified in the policy document.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAccessDenied: { + Code: "AccessDenied", + Description: "Access Denied.", + HTTPStatusCode: http.StatusForbidden, + }, + ErrBadDigest: { + Code: "BadDigest", + Description: "The Content-Md5 you specified did not match what we received.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrEntityTooSmall: { + Code: "EntityTooSmall", + Description: "Your proposed upload is smaller than the minimum allowed object size.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrEntityTooLarge: { + Code: "EntityTooLarge", + Description: "Your proposed upload exceeds the maximum allowed object size.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrPolicyTooLarge: { + Code: "PolicyTooLarge", + Description: "Policy exceeds the maximum allowed document size.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrIncompleteBody: { + Code: "IncompleteBody", + Description: "You did not provide the number of bytes specified by the Content-Length HTTP header.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInternalError: { + Code: "InternalError", + Description: "We encountered an internal error, please try again.", + HTTPStatusCode: http.StatusInternalServerError, + }, + ErrInvalidAccessKeyID: { + Code: "InvalidAccessKeyId", + Description: "The Access Key Id you provided does not exist in our records.", + HTTPStatusCode: http.StatusForbidden, + }, + ErrInvalidBucketName: { + Code: "InvalidBucketName", + Description: "The specified bucket is not valid.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidDigest: { + Code: "InvalidDigest", + Description: "The Content-Md5 you specified is not valid.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidRange: { + Code: "InvalidRange", + Description: "The requested range is not satisfiable", + HTTPStatusCode: http.StatusRequestedRangeNotSatisfiable, + }, + ErrMalformedXML: { + Code: "MalformedXML", + Description: "The XML you provided was not well-formed or did not validate against our published schema.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingContentLength: { + Code: "MissingContentLength", + Description: "You must provide the Content-Length HTTP header.", + HTTPStatusCode: http.StatusLengthRequired, + }, + ErrMissingContentMD5: { + Code: "MissingContentMD5", + Description: "Missing required header for this request: Content-Md5.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingSecurityHeader: { + Code: "MissingSecurityHeader", + Description: "Your request was missing a required header", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingRequestBodyError: { + Code: "MissingRequestBodyError", + Description: "Request body is empty.", + HTTPStatusCode: http.StatusLengthRequired, + }, + ErrNoSuchBucket: { + Code: "NoSuchBucket", + Description: "The specified bucket does not exist", + HTTPStatusCode: http.StatusNotFound, + }, + ErrNoSuchBucketPolicy: { + Code: "NoSuchBucketPolicy", + Description: "The bucket policy does not exist", + HTTPStatusCode: http.StatusNotFound, + }, + ErrNoSuchBucketLifecycle: { + Code: "NoSuchBucketLifecycle", + Description: "The bucket lifecycle configuration does not exist", + HTTPStatusCode: http.StatusNotFound, + }, + ErrNoSuchLifecycleConfiguration: { + Code: "NoSuchLifecycleConfiguration", + Description: "The lifecycle configuration does not exist", + HTTPStatusCode: http.StatusNotFound, + }, + ErrNoSuchBucketSSEConfig: { + Code: "ServerSideEncryptionConfigurationNotFoundError", + Description: "The server side encryption configuration was not found", + HTTPStatusCode: http.StatusNotFound, + }, + ErrNoSuchKey: { + Code: "NoSuchKey", + Description: "The specified key does not exist.", + HTTPStatusCode: http.StatusNotFound, + }, + ErrNoSuchUpload: { + Code: "NoSuchUpload", + Description: "The specified multipart upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.", + HTTPStatusCode: http.StatusNotFound, + }, + ErrNoSuchVersion: { + Code: "NoSuchVersion", + Description: "Indicates that the version ID specified in the request does not match an existing version.", + HTTPStatusCode: http.StatusNotFound, + }, + ErrNotImplemented: { + Code: "NotImplemented", + Description: "A header you provided implies functionality that is not implemented", + HTTPStatusCode: http.StatusNotImplemented, + }, + ErrPreconditionFailed: { + Code: "PreconditionFailed", + Description: "At least one of the pre-conditions you specified did not hold", + HTTPStatusCode: http.StatusPreconditionFailed, + }, + ErrRequestTimeTooSkewed: { + Code: "RequestTimeTooSkewed", + Description: "The difference between the request time and the server's time is too large.", + HTTPStatusCode: http.StatusForbidden, + }, + ErrSignatureDoesNotMatch: { + Code: "SignatureDoesNotMatch", + Description: "The request signature we calculated does not match the signature you provided. Check your key and signing method.", + HTTPStatusCode: http.StatusForbidden, + }, + ErrMethodNotAllowed: { + Code: "MethodNotAllowed", + Description: "The specified method is not allowed against this resource.", + HTTPStatusCode: http.StatusMethodNotAllowed, + }, + ErrInvalidPart: { + Code: "InvalidPart", + Description: "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidPartOrder: { + Code: "InvalidPartOrder", + Description: "The list of parts was not in ascending order. The parts list must be specified in order by part number.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidObjectState: { + Code: "InvalidObjectState", + Description: "The operation is not valid for the current state of the object.", + HTTPStatusCode: http.StatusForbidden, + }, + ErrAuthorizationHeaderMalformed: { + Code: "AuthorizationHeaderMalformed", + Description: "The authorization header is malformed; the region is wrong; expecting 'us-east-1'.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMalformedPOSTRequest: { + Code: "MalformedPOSTRequest", + Description: "The body of your POST request is not well-formed multipart/form-data.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrPOSTFileRequired: { + Code: "InvalidArgument", + Description: "POST requires exactly one file upload per request.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSignatureVersionNotSupported: { + Code: "InvalidRequest", + Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrBucketNotEmpty: { + Code: "BucketNotEmpty", + Description: "The bucket you tried to delete is not empty", + HTTPStatusCode: http.StatusConflict, + }, + ErrBucketAlreadyExists: { + Code: "BucketAlreadyExists", + Description: "The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.", + HTTPStatusCode: http.StatusConflict, + }, + ErrAllAccessDisabled: { + Code: "AllAccessDisabled", + Description: "All access to this bucket has been disabled.", + HTTPStatusCode: http.StatusForbidden, + }, + ErrMalformedPolicy: { + Code: "MalformedPolicy", + Description: "Policy has invalid resource.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingFields: { + Code: "MissingFields", + Description: "Missing fields in request.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingCredTag: { + Code: "InvalidRequest", + Description: "Missing Credential field for this request.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrCredMalformed: { + Code: "AuthorizationQueryParametersError", + Description: "Error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting \"/YYYYMMDD/REGION/SERVICE/aws4_request\".", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMalformedDate: { + Code: "MalformedDate", + Description: "Invalid date format header, expected to be in ISO8601, RFC1123 or RFC1123Z time format.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMalformedPresignedDate: { + Code: "AuthorizationQueryParametersError", + Description: "X-Amz-Date must be in the ISO8601 Long Format \"yyyyMMdd'T'HHmmss'Z'\"", + HTTPStatusCode: http.StatusBadRequest, + }, + // FIXME: Should contain the invalid param set as seen in https://github.com/minio/minio/issues/2385. + // right Description: "Error parsing the X-Amz-Credential parameter; incorrect date format \"%s\". This date in the credential must be in the format \"yyyyMMdd\".", + // Need changes to make sure variable messages can be constructed. + ErrMalformedCredentialDate: { + Code: "AuthorizationQueryParametersError", + Description: "Error parsing the X-Amz-Credential parameter; incorrect date format \"%s\". This date in the credential must be in the format \"yyyyMMdd\".", + HTTPStatusCode: http.StatusBadRequest, + }, + // FIXME: Should contain the invalid param set as seen in https://github.com/minio/minio/issues/2385. + // right Description: "Error parsing the X-Amz-Credential parameter; the region 'us-east-' is wrong; expecting 'us-east-1'". + // Need changes to make sure variable messages can be constructed. + ErrMalformedCredentialRegion: { + Code: "AuthorizationQueryParametersError", + Description: "Error parsing the X-Amz-Credential parameter; the region is wrong;", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidRegion: { + Code: "InvalidRegion", + Description: "Region does not match.", + HTTPStatusCode: http.StatusBadRequest, + }, + // FIXME: Should contain the invalid param set as seen in https://github.com/minio/minio/issues/2385. + // right Description: "Error parsing the X-Amz-Credential parameter; incorrect service \"s4\". This endpoint belongs to \"s3\".". + // Need changes to make sure variable messages can be constructed. + ErrInvalidServiceS3: { + Code: "AuthorizationParametersError", + Description: "Error parsing the Credential/X-Amz-Credential parameter; incorrect service. This endpoint belongs to \"s3\".", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidServiceSTS: { + Code: "AuthorizationParametersError", + Description: "Error parsing the Credential parameter; incorrect service. This endpoint belongs to \"sts\".", + HTTPStatusCode: http.StatusBadRequest, + }, + // FIXME: Should contain the invalid param set as seen in https://github.com/minio/minio/issues/2385. + // Description: "Error parsing the X-Amz-Credential parameter; incorrect terminal "aws4_reque". This endpoint uses "aws4_request". + // Need changes to make sure variable messages can be constructed. + ErrInvalidRequestVersion: { + Code: "AuthorizationQueryParametersError", + Description: "Error parsing the X-Amz-Credential parameter; incorrect terminal. This endpoint uses \"aws4_request\".", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingSignTag: { + Code: "AccessDenied", + Description: "Signature header missing Signature field.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingSignHeadersTag: { + Code: "InvalidArgument", + Description: "Signature header missing SignedHeaders field.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMalformedExpires: { + Code: "AuthorizationQueryParametersError", + Description: "X-Amz-Expires should be a number", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrNegativeExpires: { + Code: "AuthorizationQueryParametersError", + Description: "X-Amz-Expires must be non-negative", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAuthHeaderEmpty: { + Code: "InvalidArgument", + Description: "Authorization header is invalid -- one and only one ' ' (space) required.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingDateHeader: { + Code: "AccessDenied", + Description: "AWS authentication requires a valid Date or x-amz-date header", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidQuerySignatureAlgo: { + Code: "AuthorizationQueryParametersError", + Description: "X-Amz-Algorithm only supports \"AWS4-HMAC-SHA256\".", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrExpiredPresignRequest: { + Code: "AccessDenied", + Description: "Request has expired", + HTTPStatusCode: http.StatusForbidden, + }, + ErrRequestNotReadyYet: { + Code: "AccessDenied", + Description: "Request is not valid yet", + HTTPStatusCode: http.StatusForbidden, + }, + ErrSlowDown: { + Code: "SlowDown", + Description: "Please reduce your request", + HTTPStatusCode: http.StatusServiceUnavailable, + }, + ErrInvalidPrefixMarker: { + Code: "InvalidPrefixMarker", + Description: "Invalid marker prefix combination", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrBadRequest: { + Code: "BadRequest", + Description: "400 BadRequest", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrKeyTooLongError: { + Code: "KeyTooLongError", + Description: "Your key is too long", + HTTPStatusCode: http.StatusBadRequest, + }, + + // FIXME: Actual XML error response also contains the header which missed in list of signed header parameters. + ErrUnsignedHeaders: { + Code: "AccessDenied", + Description: "There were headers present in the request which were not signed", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidQueryParams: { + Code: "AuthorizationQueryParametersError", + Description: "Query-string authentication version 4 requires the X-Amz-Algorithm, X-Amz-Credential, X-Amz-Signature, X-Amz-Date, X-Amz-SignedHeaders, and X-Amz-Expires parameters.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrBucketAlreadyOwnedByYou: { + Code: "BucketAlreadyOwnedByYou", + Description: "Your previous request to create the named bucket succeeded and you already own it.", + HTTPStatusCode: http.StatusConflict, + }, + ErrInvalidDuration: { + Code: "InvalidDuration", + Description: "Duration provided in the request is invalid.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidBucketObjectLockConfiguration: { + Code: "InvalidRequest", + Description: "Bucket is missing ObjectLockConfiguration", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrBucketTaggingNotFound: { + Code: "NoSuchTagSet", + Description: "The TagSet does not exist", + HTTPStatusCode: http.StatusNotFound, + }, + ErrObjectLockConfigurationNotFound: { + Code: "ObjectLockConfigurationNotFoundError", + Description: "Object Lock configuration does not exist for this bucket", + HTTPStatusCode: http.StatusNotFound, + }, + ErrObjectLockConfigurationNotAllowed: { + Code: "InvalidBucketState", + Description: "Object Lock configuration cannot be enabled on existing buckets", + HTTPStatusCode: http.StatusConflict, + }, + ErrNoSuchCORSConfiguration: { + Code: "NoSuchCORSConfiguration", + Description: "The CORS configuration does not exist", + HTTPStatusCode: http.StatusNotFound, + }, + ErrNoSuchWebsiteConfiguration: { + Code: "NoSuchWebsiteConfiguration", + Description: "The specified bucket does not have a website configuration", + HTTPStatusCode: http.StatusNotFound, + }, + ErrReplicationConfigurationNotFoundError: { + Code: "ReplicationConfigurationNotFoundError", + Description: "The replication configuration was not found", + HTTPStatusCode: http.StatusNotFound, + }, + ErrNoSuchObjectLockConfiguration: { + Code: "NoSuchObjectLockConfiguration", + Description: "The specified object does not have a ObjectLock configuration", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrObjectLocked: { + Code: "InvalidRequest", + Description: "Object is WORM protected and cannot be overwritten", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidRetentionDate: { + Code: "InvalidRequest", + Description: "Date must be provided in ISO 8601 format", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrPastObjectLockRetainDate: { + Code: "InvalidRequest", + Description: "the retain until date must be in the future", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrUnknownWORMModeDirective: { + Code: "InvalidRequest", + Description: "unknown wormMode directive", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrObjectLockInvalidHeaders: { + Code: "InvalidRequest", + Description: "x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied", + HTTPStatusCode: http.StatusBadRequest, + }, + // Bucket notification related errors. + ErrEventNotification: { + Code: "InvalidArgument", + Description: "A specified event is not supported for notifications.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrARNNotification: { + Code: "InvalidArgument", + Description: "A specified destination ARN does not exist or is not well-formed. Verify the destination ARN.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrRegionNotification: { + Code: "InvalidArgument", + Description: "A specified destination is in a different region than the bucket. You must use a destination that resides in the same region as the bucket.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrOverlappingFilterNotification: { + Code: "InvalidArgument", + Description: "An object key name filtering rule defined with overlapping prefixes, overlapping suffixes, or overlapping combinations of prefixes and suffixes for the same event types.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrFilterNameInvalid: { + Code: "InvalidArgument", + Description: "filter rule name must be either prefix or suffix", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrFilterNamePrefix: { + Code: "InvalidArgument", + Description: "Cannot specify more than one prefix rule in a filter.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrFilterNameSuffix: { + Code: "InvalidArgument", + Description: "Cannot specify more than one suffix rule in a filter.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrFilterValueInvalid: { + Code: "InvalidArgument", + Description: "Size of filter rule value cannot exceed 1024 bytes in UTF-8 representation", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrOverlappingConfigs: { + Code: "InvalidArgument", + Description: "Configurations overlap. Configurations on the same bucket cannot share a common event type.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrUnsupportedNotification: { + Code: "UnsupportedNotification", + Description: "MinIO server does not support Topic or Cloud Function based notifications.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidCopyPartRange: { + Code: "InvalidArgument", + Description: "The x-amz-copy-source-range value must be of the form bytes=first-last where first and last are the zero-based offsets of the first and last bytes to copy", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidCopyPartRangeSource: { + Code: "InvalidArgument", + Description: "Range specified is not valid for source object", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMetadataTooLarge: { + Code: "InvalidArgument", + Description: "Your metadata headers exceed the maximum allowed metadata size.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidTagDirective: { + Code: "InvalidArgument", + Description: "Unknown tag directive.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidEncryptionMethod: { + Code: "InvalidRequest", + Description: "The encryption method specified is not supported", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInsecureSSECustomerRequest: { + Code: "InvalidRequest", + Description: "Requests specifying Server Side Encryption with Customer provided keys must be made over a secure connection.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSSEMultipartEncrypted: { + Code: "InvalidRequest", + Description: "The multipart upload initiate requested encryption. Subsequent part requests must include the appropriate encryption parameters.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSSEEncryptedObject: { + Code: "InvalidRequest", + Description: "The object was stored using a form of Server Side Encryption. The correct parameters must be provided to retrieve the object.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidEncryptionParameters: { + Code: "InvalidRequest", + Description: "The encryption parameters are not applicable to this object.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidSSECustomerAlgorithm: { + Code: "InvalidArgument", + Description: "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidSSECustomerKey: { + Code: "InvalidArgument", + Description: "The secret key was invalid for the specified algorithm.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingSSECustomerKey: { + Code: "InvalidArgument", + Description: "Requests specifying Server Side Encryption with Customer provided keys must provide an appropriate secret key.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingSSECustomerKeyMD5: { + Code: "InvalidArgument", + Description: "Requests specifying Server Side Encryption with Customer provided keys must provide the client calculated MD5 of the secret key.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSSECustomerKeyMD5Mismatch: { + Code: "InvalidArgument", + Description: "The calculated MD5 hash of the key did not match the hash that was provided.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidSSECustomerParameters: { + Code: "InvalidArgument", + Description: "The provided encryption parameters did not match the ones used originally.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrIncompatibleEncryptionMethod: { + Code: "InvalidArgument", + Description: "Server side encryption specified with both SSE-C and SSE-S3 headers", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrKMSNotConfigured: { + Code: "InvalidArgument", + Description: "Server side encryption specified but KMS is not configured", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrKMSAuthFailure: { + Code: "InvalidArgument", + Description: "Server side encryption specified but KMS authorization failed", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrNoAccessKey: { + Code: "AccessDenied", + Description: "No AWSAccessKey was presented", + HTTPStatusCode: http.StatusForbidden, + }, + ErrInvalidToken: { + Code: "InvalidTokenId", + Description: "The security token included in the request is invalid", + HTTPStatusCode: http.StatusForbidden, + }, + + // S3 extensions. + ErrContentSHA256Mismatch: { + Code: "XAmzContentSHA256Mismatch", + Description: "The provided 'x-amz-content-sha256' header does not match what was computed.", + HTTPStatusCode: http.StatusBadRequest, + }, + + // MinIO extensions. + ErrStorageFull: { + Code: "XMinioStorageFull", + Description: "Storage backend has reached its minimum free disk threshold. Please delete a few objects to proceed.", + HTTPStatusCode: http.StatusInsufficientStorage, + }, + ErrParentIsObject: { + Code: "XMinioParentIsObject", + Description: "Object-prefix is already an object, please choose a different object-prefix name.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrRequestBodyParse: { + Code: "XMinioRequestBodyParse", + Description: "The request body failed to parse.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrObjectExistsAsDirectory: { + Code: "XMinioObjectExistsAsDirectory", + Description: "Object name already exists as a directory.", + HTTPStatusCode: http.StatusConflict, + }, + ErrInvalidObjectName: { + Code: "XMinioInvalidObjectName", + Description: "Object name contains unsupported characters.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidObjectNamePrefixSlash: { + Code: "XMinioInvalidObjectName", + Description: "Object name contains a leading slash.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidResourceName: { + Code: "XMinioInvalidResourceName", + Description: "Resource name contains bad components such as \"..\" or \".\".", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrServerNotInitialized: { + Code: "XMinioServerNotInitialized", + Description: "Server not initialized, please try again.", + HTTPStatusCode: http.StatusServiceUnavailable, + }, + ErrMalformedJSON: { + Code: "XMinioMalformedJSON", + Description: "The JSON you provided was not well-formed or did not validate against our published format.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminNoSuchUser: { + Code: "XMinioAdminNoSuchUser", + Description: "The specified user does not exist.", + HTTPStatusCode: http.StatusNotFound, + }, + ErrAdminNoSuchGroup: { + Code: "XMinioAdminNoSuchGroup", + Description: "The specified group does not exist.", + HTTPStatusCode: http.StatusNotFound, + }, + ErrAdminGroupNotEmpty: { + Code: "XMinioAdminGroupNotEmpty", + Description: "The specified group is not empty - cannot remove it.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminNoSuchPolicy: { + Code: "XMinioAdminNoSuchPolicy", + Description: "The canned policy does not exist.", + HTTPStatusCode: http.StatusNotFound, + }, + ErrAdminInvalidArgument: { + Code: "XMinioAdminInvalidArgument", + Description: "Invalid arguments specified.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminInvalidAccessKey: { + Code: "XMinioAdminInvalidAccessKey", + Description: "The access key is invalid.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminInvalidSecretKey: { + Code: "XMinioAdminInvalidSecretKey", + Description: "The secret key is invalid.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminConfigNoQuorum: { + Code: "XMinioAdminConfigNoQuorum", + Description: "Configuration update failed because server quorum was not met", + HTTPStatusCode: http.StatusServiceUnavailable, + }, + ErrAdminConfigTooLarge: { + Code: "XMinioAdminConfigTooLarge", + Description: fmt.Sprintf("Configuration data provided exceeds the allowed maximum of %d bytes", + maxEConfigJSONSize), + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminConfigBadJSON: { + Code: "XMinioAdminConfigBadJSON", + Description: "JSON configuration provided is of incorrect format", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminConfigDuplicateKeys: { + Code: "XMinioAdminConfigDuplicateKeys", + Description: "JSON configuration provided has objects with duplicate keys", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminConfigNotificationTargetsFailed: { + Code: "XMinioAdminNotificationTargetsTestFailed", + Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminProfilerNotEnabled: { + Code: "XMinioAdminProfilerNotEnabled", + Description: "Unable to perform the requested operation because profiling is not enabled", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminCredentialsMismatch: { + Code: "XMinioAdminCredentialsMismatch", + Description: "Credentials in config mismatch with server environment variables", + HTTPStatusCode: http.StatusServiceUnavailable, + }, + ErrAdminBucketQuotaExceeded: { + Code: "XMinioAdminBucketQuotaExceeded", + Description: "Bucket quota exceeded", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAdminNoSuchQuotaConfiguration: { + Code: "XMinioAdminNoSuchQuotaConfiguration", + Description: "The quota configuration does not exist", + HTTPStatusCode: http.StatusNotFound, + }, + ErrAdminBucketQuotaDisabled: { + Code: "XMinioAdminBucketQuotaDisabled", + Description: "Quota specified but disk usage crawl is disabled on MinIO server", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInsecureClientRequest: { + Code: "XMinioInsecureClientRequest", + Description: "Cannot respond to plain-text request from TLS-encrypted server", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrOperationTimedOut: { + Code: "RequestTimeout", + Description: "A timeout occurred while trying to lock a resource, please reduce your request rate", + HTTPStatusCode: http.StatusServiceUnavailable, + }, + ErrOperationMaxedOut: { + Code: "SlowDown", + Description: "A timeout exceeded while waiting to proceed with the request, please reduce your request rate", + HTTPStatusCode: http.StatusServiceUnavailable, + }, + ErrUnsupportedMetadata: { + Code: "InvalidArgument", + Description: "Your metadata headers are not supported.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrObjectTampered: { + Code: "XMinioObjectTampered", + Description: errObjectTampered.Error(), + HTTPStatusCode: http.StatusPartialContent, + }, + ErrMaximumExpires: { + Code: "AuthorizationQueryParametersError", + Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds", + HTTPStatusCode: http.StatusBadRequest, + }, + + // Generic Invalid-Request error. Should be used for response errors only for unlikely + // corner case errors for which introducing new ErrorCode is not worth it. LogIf() + // should be used to log the error at the source of the error for debugging purposes. + ErrInvalidRequest: { + Code: "InvalidRequest", + Description: "Invalid Request", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrHealNotImplemented: { + Code: "XMinioHealNotImplemented", + Description: "This server does not implement heal functionality.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrHealNoSuchProcess: { + Code: "XMinioHealNoSuchProcess", + Description: "No such heal process is running on the server", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrHealInvalidClientToken: { + Code: "XMinioHealInvalidClientToken", + Description: "Client token mismatch", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrHealMissingBucket: { + Code: "XMinioHealMissingBucket", + Description: "A heal start request with a non-empty object-prefix parameter requires a bucket to be specified.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrHealAlreadyRunning: { + Code: "XMinioHealAlreadyRunning", + Description: "", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrHealOverlappingPaths: { + Code: "XMinioHealOverlappingPaths", + Description: "", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrBackendDown: { + Code: "XMinioBackendDown", + Description: "Object storage backend is unreachable", + HTTPStatusCode: http.StatusServiceUnavailable, + }, + ErrIncorrectContinuationToken: { + Code: "InvalidArgument", + Description: "The continuation token provided is incorrect", + HTTPStatusCode: http.StatusBadRequest, + }, + + // S3 Select API Errors + ErrEmptyRequestBody: { + Code: "EmptyRequestBody", + Description: "Request body cannot be empty.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrUnsupportedFunction: { + Code: "UnsupportedFunction", + Description: "Encountered an unsupported SQL function.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidDataSource: { + Code: "InvalidDataSource", + Description: "Invalid data source type. Only CSV and JSON are supported at this time.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidExpressionType: { + Code: "InvalidExpressionType", + Description: "The ExpressionType is invalid. Only SQL expressions are supported at this time.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrBusy: { + Code: "Busy", + Description: "The service is unavailable. Please retry.", + HTTPStatusCode: http.StatusServiceUnavailable, + }, + ErrUnauthorizedAccess: { + Code: "UnauthorizedAccess", + Description: "You are not authorized to perform this operation", + HTTPStatusCode: http.StatusUnauthorized, + }, + ErrExpressionTooLong: { + Code: "ExpressionTooLong", + Description: "The SQL expression is too long: The maximum byte-length for the SQL expression is 256 KB.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrIllegalSQLFunctionArgument: { + Code: "IllegalSqlFunctionArgument", + Description: "Illegal argument was used in the SQL function.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidKeyPath: { + Code: "InvalidKeyPath", + Description: "Key path in the SQL expression is invalid.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidCompressionFormat: { + Code: "InvalidCompressionFormat", + Description: "The file is not in a supported compression format. Only GZIP is supported at this time.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidFileHeaderInfo: { + Code: "InvalidFileHeaderInfo", + Description: "The FileHeaderInfo is invalid. Only NONE, USE, and IGNORE are supported.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidJSONType: { + Code: "InvalidJsonType", + Description: "The JsonType is invalid. Only DOCUMENT and LINES are supported at this time.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidQuoteFields: { + Code: "InvalidQuoteFields", + Description: "The QuoteFields is invalid. Only ALWAYS and ASNEEDED are supported.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidRequestParameter: { + Code: "InvalidRequestParameter", + Description: "The value of a parameter in SelectRequest element is invalid. Check the service API documentation and try again.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidDataType: { + Code: "InvalidDataType", + Description: "The SQL expression contains an invalid data type.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidTextEncoding: { + Code: "InvalidTextEncoding", + Description: "Invalid encoding type. Only UTF-8 encoding is supported at this time.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidTableAlias: { + Code: "InvalidTableAlias", + Description: "The SQL expression contains an invalid table alias.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingRequiredParameter: { + Code: "MissingRequiredParameter", + Description: "The SelectRequest entity is missing a required parameter. Check the service documentation and try again.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrObjectSerializationConflict: { + Code: "ObjectSerializationConflict", + Description: "The SelectRequest entity can only contain one of CSV or JSON. Check the service documentation and try again.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrUnsupportedSQLOperation: { + Code: "UnsupportedSqlOperation", + Description: "Encountered an unsupported SQL operation.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrUnsupportedSQLStructure: { + Code: "UnsupportedSqlStructure", + Description: "Encountered an unsupported SQL structure. Check the SQL Reference.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrUnsupportedSyntax: { + Code: "UnsupportedSyntax", + Description: "Encountered invalid syntax.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrUnsupportedRangeHeader: { + Code: "UnsupportedRangeHeader", + Description: "Range header is not supported for this operation.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrLexerInvalidChar: { + Code: "LexerInvalidChar", + Description: "The SQL expression contains an invalid character.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrLexerInvalidOperator: { + Code: "LexerInvalidOperator", + Description: "The SQL expression contains an invalid literal.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrLexerInvalidLiteral: { + Code: "LexerInvalidLiteral", + Description: "The SQL expression contains an invalid operator.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrLexerInvalidIONLiteral: { + Code: "LexerInvalidIONLiteral", + Description: "The SQL expression contains an invalid operator.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedDatePart: { + Code: "ParseExpectedDatePart", + Description: "Did not find the expected date part in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedKeyword: { + Code: "ParseExpectedKeyword", + Description: "Did not find the expected keyword in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedTokenType: { + Code: "ParseExpectedTokenType", + Description: "Did not find the expected token in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpected2TokenTypes: { + Code: "ParseExpected2TokenTypes", + Description: "Did not find the expected token in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedNumber: { + Code: "ParseExpectedNumber", + Description: "Did not find the expected number in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedRightParenBuiltinFunctionCall: { + Code: "ParseExpectedRightParenBuiltinFunctionCall", + Description: "Did not find the expected right parenthesis character in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedTypeName: { + Code: "ParseExpectedTypeName", + Description: "Did not find the expected type name in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedWhenClause: { + Code: "ParseExpectedWhenClause", + Description: "Did not find the expected WHEN clause in the SQL expression. CASE is not supported.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnsupportedToken: { + Code: "ParseUnsupportedToken", + Description: "The SQL expression contains an unsupported token.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnsupportedLiteralsGroupBy: { + Code: "ParseUnsupportedLiteralsGroupBy", + Description: "The SQL expression contains an unsupported use of GROUP BY.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedMember: { + Code: "ParseExpectedMember", + Description: "The SQL expression contains an unsupported use of MEMBER.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnsupportedSelect: { + Code: "ParseUnsupportedSelect", + Description: "The SQL expression contains an unsupported use of SELECT.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnsupportedCase: { + Code: "ParseUnsupportedCase", + Description: "The SQL expression contains an unsupported use of CASE.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnsupportedCaseClause: { + Code: "ParseUnsupportedCaseClause", + Description: "The SQL expression contains an unsupported use of CASE.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnsupportedAlias: { + Code: "ParseUnsupportedAlias", + Description: "The SQL expression contains an unsupported use of ALIAS.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnsupportedSyntax: { + Code: "ParseUnsupportedSyntax", + Description: "The SQL expression contains unsupported syntax.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnknownOperator: { + Code: "ParseUnknownOperator", + Description: "The SQL expression contains an invalid operator.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseMissingIdentAfterAt: { + Code: "ParseMissingIdentAfterAt", + Description: "Did not find the expected identifier after the @ symbol in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnexpectedOperator: { + Code: "ParseUnexpectedOperator", + Description: "The SQL expression contains an unexpected operator.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnexpectedTerm: { + Code: "ParseUnexpectedTerm", + Description: "The SQL expression contains an unexpected term.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnexpectedToken: { + Code: "ParseUnexpectedToken", + Description: "The SQL expression contains an unexpected token.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnexpectedKeyword: { + Code: "ParseUnexpectedKeyword", + Description: "The SQL expression contains an unexpected keyword.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedExpression: { + Code: "ParseExpectedExpression", + Description: "Did not find the expected SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedLeftParenAfterCast: { + Code: "ParseExpectedLeftParenAfterCast", + Description: "Did not find expected the left parenthesis in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedLeftParenValueConstructor: { + Code: "ParseExpectedLeftParenValueConstructor", + Description: "Did not find expected the left parenthesis in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedLeftParenBuiltinFunctionCall: { + Code: "ParseExpectedLeftParenBuiltinFunctionCall", + Description: "Did not find the expected left parenthesis in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedArgumentDelimiter: { + Code: "ParseExpectedArgumentDelimiter", + Description: "Did not find the expected argument delimiter in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseCastArity: { + Code: "ParseCastArity", + Description: "The SQL expression CAST has incorrect arity.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseInvalidTypeParam: { + Code: "ParseInvalidTypeParam", + Description: "The SQL expression contains an invalid parameter value.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseEmptySelect: { + Code: "ParseEmptySelect", + Description: "The SQL expression contains an empty SELECT.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseSelectMissingFrom: { + Code: "ParseSelectMissingFrom", + Description: "GROUP is not supported in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedIdentForGroupName: { + Code: "ParseExpectedIdentForGroupName", + Description: "GROUP is not supported in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedIdentForAlias: { + Code: "ParseExpectedIdentForAlias", + Description: "Did not find the expected identifier for the alias in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseUnsupportedCallWithStar: { + Code: "ParseUnsupportedCallWithStar", + Description: "Only COUNT with (*) as a parameter is supported in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseNonUnaryAgregateFunctionCall: { + Code: "ParseNonUnaryAgregateFunctionCall", + Description: "Only one argument is supported for aggregate functions in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseMalformedJoin: { + Code: "ParseMalformedJoin", + Description: "JOIN is not supported in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseExpectedIdentForAt: { + Code: "ParseExpectedIdentForAt", + Description: "Did not find the expected identifier for AT name in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseAsteriskIsNotAloneInSelectList: { + Code: "ParseAsteriskIsNotAloneInSelectList", + Description: "Other expressions are not allowed in the SELECT list when '*' is used without dot notation in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseCannotMixSqbAndWildcardInSelectList: { + Code: "ParseCannotMixSqbAndWildcardInSelectList", + Description: "Cannot mix [] and * in the same expression in a SELECT list in SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrParseInvalidContextForWildcardInSelectList: { + Code: "ParseInvalidContextForWildcardInSelectList", + Description: "Invalid use of * in SELECT list in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrIncorrectSQLFunctionArgumentType: { + Code: "IncorrectSqlFunctionArgumentType", + Description: "Incorrect type of arguments in function call in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrValueParseFailure: { + Code: "ValueParseFailure", + Description: "Time stamp parse failure in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrEvaluatorInvalidArguments: { + Code: "EvaluatorInvalidArguments", + Description: "Incorrect number of arguments in the function call in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrIntegerOverflow: { + Code: "IntegerOverflow", + Description: "Int overflow or underflow in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrLikeInvalidInputs: { + Code: "LikeInvalidInputs", + Description: "Invalid argument given to the LIKE clause in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrCastFailed: { + Code: "CastFailed", + Description: "Attempt to convert from one data type to another using CAST failed in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidCast: { + Code: "InvalidCast", + Description: "Attempt to convert from one data type to another using CAST failed in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrEvaluatorInvalidTimestampFormatPattern: { + Code: "EvaluatorInvalidTimestampFormatPattern", + Description: "Time stamp format pattern requires additional fields in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrEvaluatorInvalidTimestampFormatPatternSymbolForParsing: { + Code: "EvaluatorInvalidTimestampFormatPatternSymbolForParsing", + Description: "Time stamp format pattern contains a valid format symbol that cannot be applied to time stamp parsing in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrEvaluatorTimestampFormatPatternDuplicateFields: { + Code: "EvaluatorTimestampFormatPatternDuplicateFields", + Description: "Time stamp format pattern contains multiple format specifiers representing the time stamp field in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrEvaluatorTimestampFormatPatternHourClockAmPmMismatch: { + Code: "EvaluatorUnterminatedTimestampFormatPatternToken", + Description: "Time stamp format pattern contains unterminated token in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrEvaluatorUnterminatedTimestampFormatPatternToken: { + Code: "EvaluatorInvalidTimestampFormatPatternToken", + Description: "Time stamp format pattern contains an invalid token in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrEvaluatorInvalidTimestampFormatPatternToken: { + Code: "EvaluatorInvalidTimestampFormatPatternToken", + Description: "Time stamp format pattern contains an invalid token in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrEvaluatorInvalidTimestampFormatPatternSymbol: { + Code: "EvaluatorInvalidTimestampFormatPatternSymbol", + Description: "Time stamp format pattern contains an invalid symbol in the SQL expression.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrEvaluatorBindingDoesNotExist: { + Code: "ErrEvaluatorBindingDoesNotExist", + Description: "A column name or a path provided does not exist in the SQL expression", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingHeaders: { + Code: "MissingHeaders", + Description: "Some headers in the query are missing from the file. Check the file and try again.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidColumnIndex: { + Code: "InvalidColumnIndex", + Description: "The column index is invalid. Please check the service documentation and try again.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidDecompressedSize: { + Code: "XMinioInvalidDecompressedSize", + Description: "The data provided is unfit for decompression", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrAddUserInvalidArgument: { + Code: "XMinioInvalidIAMCredentials", + Description: "User is not allowed to be same as admin access key", + HTTPStatusCode: http.StatusConflict, + }, + ErrAdminAccountNotEligible: { + Code: "XMinioInvalidIAMCredentials", + Description: "The administrator key is not eligible for this operation", + HTTPStatusCode: http.StatusConflict, + }, + ErrServiceAccountNotFound: { + Code: "XMinioInvalidIAMCredentials", + Description: "The specified service account is not found", + HTTPStatusCode: http.StatusNotFound, + }, + ErrPostPolicyConditionInvalidFormat: { + Code: "PostPolicyInvalidKeyName", + Description: "Invalid according to Policy: Policy Condition failed", + HTTPStatusCode: http.StatusForbidden, + }, + // Add your error structure here. +} + +func (e errorCodeMap) ToAPIErrWithErr(errCode ErrorCode, err error) Error { + apiErr, ok := e[errCode] + if !ok { + apiErr = e[ErrInternalError] + } + if err != nil { + apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err) + } + return apiErr +} + +func (e errorCodeMap) ToAPIErr(errCode ErrorCode) Error { + return e.ToAPIErrWithErr(errCode, nil) +} + +// toAPIErrorCode - Converts embedded errors. Convenience +// function written to handle all cases where we have known types of +// errors returned by underlying layers. +func toAPIErrorCode(ctx context.Context, err error) (apiErr ErrorCode) { + if err == nil { + return ErrNone + } + + switch err { + case errInvalidArgument: + apiErr = ErrAdminInvalidArgument + case errNoSuchUser: + apiErr = ErrAdminNoSuchUser + case errNoSuchGroup: + apiErr = ErrAdminNoSuchGroup + case errGroupNotEmpty: + apiErr = ErrAdminGroupNotEmpty + case errNoSuchPolicy: + apiErr = ErrAdminNoSuchPolicy + case errSignatureMismatch: + apiErr = ErrSignatureDoesNotMatch + case errInvalidRange: + apiErr = ErrInvalidRange + case errDataTooLarge: + apiErr = ErrEntityTooLarge + case errDataTooSmall: + apiErr = ErrEntityTooSmall + case errAuthentication: + apiErr = ErrAccessDenied + case auth.ErrInvalidAccessKeyLength: + apiErr = ErrAdminInvalidAccessKey + case auth.ErrInvalidSecretKeyLength: + apiErr = ErrAdminInvalidSecretKey + // SSE errors + case errInvalidEncryptionParameters: + apiErr = ErrInvalidEncryptionParameters + case crypto.ErrInvalidEncryptionMethod: + apiErr = ErrInvalidEncryptionMethod + case crypto.ErrInvalidCustomerAlgorithm: + apiErr = ErrInvalidSSECustomerAlgorithm + case crypto.ErrMissingCustomerKey: + apiErr = ErrMissingSSECustomerKey + case crypto.ErrMissingCustomerKeyMD5: + apiErr = ErrMissingSSECustomerKeyMD5 + case crypto.ErrCustomerKeyMD5Mismatch: + apiErr = ErrSSECustomerKeyMD5Mismatch + case errObjectTampered: + apiErr = ErrObjectTampered + case errEncryptedObject: + apiErr = ErrSSEEncryptedObject + case errInvalidSSEParameters: + apiErr = ErrInvalidSSECustomerParameters + case crypto.ErrInvalidCustomerKey, crypto.ErrSecretKeyMismatch: + apiErr = ErrAccessDenied // no access without correct key + case crypto.ErrIncompatibleEncryptionMethod: + apiErr = ErrIncompatibleEncryptionMethod + case errKMSNotConfigured: + apiErr = ErrKMSNotConfigured + case crypto.ErrKMSAuthLogin: + apiErr = ErrKMSAuthFailure + case context.Canceled, context.DeadlineExceeded: + apiErr = ErrOperationTimedOut + case errDiskNotFound: + apiErr = ErrSlowDown + case objectlock.ErrInvalidRetentionDate: + apiErr = ErrInvalidRetentionDate + case objectlock.ErrPastObjectLockRetainDate: + apiErr = ErrPastObjectLockRetainDate + case objectlock.ErrUnknownWORMModeDirective: + apiErr = ErrUnknownWORMModeDirective + case objectlock.ErrObjectLockInvalidHeaders: + apiErr = ErrObjectLockInvalidHeaders + case objectlock.ErrMalformedXML: + apiErr = ErrMalformedXML + } + + // Compression errors + switch err { + case errInvalidDecompressedSize: + apiErr = ErrInvalidDecompressedSize + } + + if apiErr != ErrNone { + // If there was a match in the above switch case. + return apiErr + } + + // etcd specific errors, a key is always a bucket for us return + // ErrNoSuchBucket in such a case. + if err == ErrNoEntriesFound { + return ErrNoSuchBucket + } + + switch err.(type) { + case StorageFull: + apiErr = ErrStorageFull + case hash.BadDigest: + apiErr = ErrBadDigest + case AllAccessDisabled: + apiErr = ErrAllAccessDisabled + case IncompleteBody: + apiErr = ErrIncompleteBody + case ObjectExistsAsDirectory: + apiErr = ErrObjectExistsAsDirectory + case PrefixAccessDenied: + apiErr = ErrAccessDenied + case ParentIsObject: + apiErr = ErrParentIsObject + case BucketNameInvalid: + apiErr = ErrInvalidBucketName + case BucketNotFound: + apiErr = ErrNoSuchBucket + case BucketAlreadyOwnedByYou: + apiErr = ErrBucketAlreadyOwnedByYou + case BucketNotEmpty: + apiErr = ErrBucketNotEmpty + case BucketAlreadyExists: + apiErr = ErrBucketAlreadyExists + case BucketExists: + apiErr = ErrBucketAlreadyOwnedByYou + case ObjectNotFound: + apiErr = ErrNoSuchKey + case ObjectAlreadyExists: + apiErr = ErrMethodNotAllowed + case ObjectNameInvalid: + apiErr = ErrInvalidObjectName + case ObjectNamePrefixAsSlash: + apiErr = ErrInvalidObjectNamePrefixSlash + case InvalidUploadID: + apiErr = ErrNoSuchUpload + case InvalidPart: + apiErr = ErrInvalidPart + case InsufficientWriteQuorum: + apiErr = ErrSlowDown + case InsufficientReadQuorum: + apiErr = ErrSlowDown + case UnsupportedDelimiter: + apiErr = ErrNotImplemented + case InvalidMarkerPrefixCombination: + apiErr = ErrNotImplemented + case InvalidUploadIDKeyCombination: + apiErr = ErrNotImplemented + case MalformedUploadID: + apiErr = ErrNoSuchUpload + case PartTooSmall: + apiErr = ErrEntityTooSmall + case SignatureDoesNotMatch: + apiErr = ErrSignatureDoesNotMatch + case hash.SHA256Mismatch: + apiErr = ErrContentSHA256Mismatch + case ObjectTooLarge: + apiErr = ErrEntityTooLarge + case ObjectTooSmall: + apiErr = ErrEntityTooSmall + case NotImplemented: + apiErr = ErrNotImplemented + case PartTooBig: + apiErr = ErrEntityTooLarge + case UnsupportedMetadata: + apiErr = ErrUnsupportedMetadata + case BucketPolicyNotFound: + apiErr = ErrNoSuchBucketPolicy + case BucketLifecycleNotFound: + apiErr = ErrNoSuchLifecycleConfiguration + case BucketSSEConfigNotFound: + apiErr = ErrNoSuchBucketSSEConfig + case BucketTaggingNotFound: + apiErr = ErrBucketTaggingNotFound + case BucketObjectLockConfigNotFound: + apiErr = ErrObjectLockConfigurationNotFound + case BucketQuotaConfigNotFound: + apiErr = ErrAdminNoSuchQuotaConfiguration + case BucketQuotaExceeded: + apiErr = ErrAdminBucketQuotaExceeded + case *event.ErrInvalidEventName: + apiErr = ErrEventNotification + case *event.ErrInvalidARN: + apiErr = ErrARNNotification + case *event.ErrARNNotFound: + apiErr = ErrARNNotification + case *event.ErrUnknownRegion: + apiErr = ErrRegionNotification + case *event.ErrInvalidFilterName: + apiErr = ErrFilterNameInvalid + case *event.ErrFilterNamePrefix: + apiErr = ErrFilterNamePrefix + case *event.ErrFilterNameSuffix: + apiErr = ErrFilterNameSuffix + case *event.ErrInvalidFilterValue: + apiErr = ErrFilterValueInvalid + case *event.ErrDuplicateEventName: + apiErr = ErrOverlappingConfigs + case *event.ErrDuplicateQueueConfiguration: + apiErr = ErrOverlappingFilterNotification + case *event.ErrUnsupportedConfiguration: + apiErr = ErrUnsupportedNotification + case OperationTimedOut: + apiErr = ErrOperationTimedOut + case BackendDown: + apiErr = ErrBackendDown + case ObjectNameTooLong: + apiErr = ErrKeyTooLongError + default: + var ie, iw int + // This work-around is to handle the issue golang/go#30648 + if _, ferr := fmt.Fscanf(strings.NewReader(err.Error()), + "request declared a Content-Length of %d but only wrote %d bytes", + &ie, &iw); ferr != nil { + apiErr = ErrInternalError + // Make sure to log the errors which we cannot translate + // to a meaningful S3 API errors. This is added to aid in + // debugging unexpected/unhandled errors. + // logger.LogIf(ctx, err) + } else if ie > iw { + apiErr = ErrIncompleteBody + } else { + apiErr = ErrInternalError + // Make sure to log the errors which we cannot translate + // to a meaningful S3 API errors. This is added to aid in + // debugging unexpected/unhandled errors. + // logger.LogIf(ctx, err) + } + } + + return apiErr +} + +var noError = Error{} + +// toAPIError - Converts embedded errors. Convenience +// function written to handle all cases where we have known types of +// errors returned by underlying layers. +func toAPIError(ctx context.Context, err error) Error { + if err == nil { + return noError + } + + var apiErr = errorCodes.ToAPIErr(toAPIErrorCode(ctx, err)) + if apiErr.Code == "InternalError" { + // If we see an internal error try to interpret + // any underlying errors if possible depending on + // their internal error types. This code is only + // useful with gateway implementations. + switch e := err.(type) { + case *xml.SyntaxError: + apiErr = Error{ + Code: "MalformedXML", + Description: fmt.Sprintf("%s (%s)", errorCodes[ErrMalformedXML].Description, + e.Error()), + HTTPStatusCode: errorCodes[ErrMalformedXML].HTTPStatusCode, + } + case url.EscapeError: + apiErr = Error{ + Code: "XMinioInvalidObjectName", + Description: fmt.Sprintf("%s (%s)", errorCodes[ErrInvalidObjectName].Description, + e.Error()), + HTTPStatusCode: http.StatusBadRequest, + } + case lifecycle.Error: + apiErr = Error{ + Code: "InvalidRequest", + Description: e.Error(), + HTTPStatusCode: http.StatusBadRequest, + } + case tags.Error: + apiErr = Error{ + Code: e.Code(), + Description: e.Error(), + HTTPStatusCode: http.StatusBadRequest, + } + case policy.Error: + apiErr = Error{ + Code: "MalformedPolicy", + Description: e.Error(), + HTTPStatusCode: http.StatusBadRequest, + } + case crypto.Error: + apiErr = Error{ + Code: "XMinIOEncryptionError", + Description: e.Error(), + HTTPStatusCode: http.StatusBadRequest, + } + case minio.ErrorResponse: + apiErr = Error{ + Code: e.Code, + Description: e.Message, + HTTPStatusCode: e.StatusCode, + } + case *googleapi.Error: + apiErr = Error{ + Code: "XGCSInternalError", + Description: e.Message, + HTTPStatusCode: e.Code, + } + // GCS may send multiple errors, just pick the first one + // since S3 only sends one Error XML response. + if len(e.Errors) >= 1 { + apiErr.Code = e.Errors[0].Reason + + } + case azblob.StorageError: + apiErr = Error{ + Code: string(e.ServiceCode()), + Description: e.Error(), + HTTPStatusCode: e.Response().StatusCode, + } + // Add more Gateway SDKs here if any in future. + } + } + + return apiErr +} + +// getAPIError provides API Error for input API error code. +func getAPIError(code ErrorCode) Error { + if apiErr, ok := errorCodes[code]; ok { + return apiErr + } + return errorCodes.ToAPIErr(ErrInternalError) +} + +// getErrorResponse gets in standard error and resource value and +// provides a encodable populated response values +func getAPIErrorResponse(ctx context.Context, err Error, resource, requestID, hostID string) ErrorResponse { + reqInfo := GetReqInfo(ctx) + return ErrorResponse{ + Code: err.Code, + Message: err.Description, + BucketName: reqInfo.BucketName, + Key: reqInfo.ObjectName, + Resource: resource, + Region: "", + RequestID: requestID, + HostID: hostID, + } +} diff --git a/neofs/api/handler/unimplemented.go b/neofs/api/handler/unimplemented.go new file mode 100644 index 00000000..7d10edb8 --- /dev/null +++ b/neofs/api/handler/unimplemented.go @@ -0,0 +1,501 @@ +package handler + +import ( + "net/http" + + "github.com/minio/minio/neofs/api" +) + +type handler struct{} + +var _ api.Handler = (*handler)(nil) + +func New() (api.Handler, error) { return new(handler), nil } + +func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) CopyObjectPartHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutObjectPartHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) ListObjectPartsHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) NewMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) SelectObjectContentHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetObjectRetentionHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketAccelerateHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketRequestPaymentHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketLoggingHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketReplicationHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) DeleteBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) GetBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) ListenBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) ListObjectsV2MHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) ListBucketObjectVersionsHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PutBucketHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) HeadBucketHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) DeleteBucketEncryptionHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} + +func (h *handler) ListBucketsHandler(w http.ResponseWriter, r *http.Request) { + api.WriteErrorResponse(r.Context(), w, api.Error{ + Code: "XNeoFSUnimplemented", + Description: "implement me", + HTTPStatusCode: http.StatusNotImplemented, + }, r.URL) +} diff --git a/neofs/api/max-clients.go b/neofs/api/max-clients.go new file mode 100644 index 00000000..93902f04 --- /dev/null +++ b/neofs/api/max-clients.go @@ -0,0 +1,56 @@ +package api + +import ( + "net/http" + "time" +) + +type ( + MaxClients interface { + Handle(http.HandlerFunc) http.HandlerFunc + } + + maxClients struct { + pool chan struct{} + timeout time.Duration + } +) + +const defaultRequestDeadline = time.Second * 30 + +func NewMaxClientsMiddleware(count int, timeout time.Duration) MaxClients { + if timeout <= 0 { + timeout = defaultRequestDeadline + } + + return &maxClients{ + pool: make(chan struct{}, count), + timeout: timeout, + } +} + +func (m *maxClients) Handle(f http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if m.pool == nil { + f.ServeHTTP(w, r) + return + } + + deadline := time.NewTimer(m.timeout) + defer deadline.Stop() + + select { + case m.pool <- struct{}{}: + defer func() { <-m.pool }() + f.ServeHTTP(w, r) + case <-deadline.C: + // Send a http timeout message + WriteErrorResponse(r.Context(), w, + errorCodes.ToAPIErr(ErrOperationMaxedOut), + r.URL) + return + case <-r.Context().Done(): + return + } + } +} diff --git a/neofs/api/object-errors.go b/neofs/api/object-errors.go new file mode 100644 index 00000000..be10d1ee --- /dev/null +++ b/neofs/api/object-errors.go @@ -0,0 +1,474 @@ +package api + +import ( + "context" + "errors" + "fmt" + "io" + "path" +) + +// Converts underlying storage error. Convenience function written to +// handle all cases where we have known types of errors returned by +// underlying storage layer. +func toObjectErr(err error, params ...string) error { + switch err { + case errVolumeNotFound: + if len(params) >= 1 { + err = BucketNotFound{Bucket: params[0]} + } + case errVolumeNotEmpty: + if len(params) >= 1 { + err = BucketNotEmpty{Bucket: params[0]} + } + case errVolumeExists: + if len(params) >= 1 { + err = BucketExists{Bucket: params[0]} + } + case errDiskFull: + err = StorageFull{} + case errTooManyOpenFiles: + err = SlowDown{} + case errFileAccessDenied: + if len(params) >= 2 { + err = PrefixAccessDenied{ + Bucket: params[0], + Object: params[1], + } + } + case errFileParentIsFile: + if len(params) >= 2 { + err = ParentIsObject{ + Bucket: params[0], + Object: params[1], + } + } + case errIsNotRegular: + if len(params) >= 2 { + err = ObjectExistsAsDirectory{ + Bucket: params[0], + Object: params[1], + } + } + case errFileNotFound: + switch len(params) { + case 2: + err = ObjectNotFound{ + Bucket: params[0], + Object: params[1], + } + case 3: + err = InvalidUploadID{ + Bucket: params[0], + Object: params[1], + UploadID: params[2], + } + } + case errFileNameTooLong: + if len(params) >= 2 { + err = ObjectNameInvalid{ + Bucket: params[0], + Object: params[1], + } + } + case errDataTooLarge: + if len(params) >= 2 { + err = ObjectTooLarge{ + Bucket: params[0], + Object: params[1], + } + } + case errDataTooSmall: + if len(params) >= 2 { + err = ObjectTooSmall{ + Bucket: params[0], + Object: params[1], + } + } + case errXLReadQuorum: + err = InsufficientReadQuorum{} + case errXLWriteQuorum: + err = InsufficientWriteQuorum{} + case io.ErrUnexpectedEOF, io.ErrShortWrite: + err = IncompleteBody{} + case context.Canceled, context.DeadlineExceeded: + err = IncompleteBody{} + } + return err +} + +// SignatureDoesNotMatch - when content md5 does not match with what was sent from client. +type SignatureDoesNotMatch struct{} + +func (e SignatureDoesNotMatch) Error() string { + return "The request signature we calculated does not match the signature you provided. Check your key and signing method." +} + +// StorageFull storage ran out of space. +type StorageFull struct{} + +func (e StorageFull) Error() string { + return "Storage reached its minimum free disk threshold." +} + +// SlowDown too many file descriptors open or backend busy . +type SlowDown struct{} + +func (e SlowDown) Error() string { + return "Please reduce your request rate" +} + +// InsufficientReadQuorum storage cannot satisfy quorum for read operation. +type InsufficientReadQuorum struct{} + +func (e InsufficientReadQuorum) Error() string { + return "Storage resources are insufficient for the read operation." +} + +// InsufficientWriteQuorum storage cannot satisfy quorum for write operation. +type InsufficientWriteQuorum struct{} + +func (e InsufficientWriteQuorum) Error() string { + return "Storage resources are insufficient for the write operation." +} + +// GenericError - generic object layer error. +type GenericError struct { + Bucket string + Object string +} + +// BucketNotFound bucket does not exist. +type BucketNotFound GenericError + +func (e BucketNotFound) Error() string { + return "Bucket not found: " + e.Bucket +} + +// BucketAlreadyExists the requested bucket name is not available. +type BucketAlreadyExists GenericError + +func (e BucketAlreadyExists) Error() string { + return "The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again." +} + +// BucketAlreadyOwnedByYou already owned by you. +type BucketAlreadyOwnedByYou GenericError + +func (e BucketAlreadyOwnedByYou) Error() string { + return "Bucket already owned by you: " + e.Bucket +} + +// BucketNotEmpty bucket is not empty. +type BucketNotEmpty GenericError + +func (e BucketNotEmpty) Error() string { + return "Bucket not empty: " + e.Bucket +} + +// ObjectNotFound object does not exist. +type ObjectNotFound GenericError + +func (e ObjectNotFound) Error() string { + return "Object not found: " + e.Bucket + "#" + e.Object +} + +// ObjectAlreadyExists object already exists. +type ObjectAlreadyExists GenericError + +func (e ObjectAlreadyExists) Error() string { + return "Object: " + e.Bucket + "#" + e.Object + " already exists" +} + +// ObjectExistsAsDirectory object already exists as a directory. +type ObjectExistsAsDirectory GenericError + +func (e ObjectExistsAsDirectory) Error() string { + return "Object exists on : " + e.Bucket + " as directory " + e.Object +} + +// PrefixAccessDenied object access is denied. +type PrefixAccessDenied GenericError + +func (e PrefixAccessDenied) Error() string { + return "Prefix access is denied: " + e.Bucket + SlashSeparator + e.Object +} + +// ParentIsObject object access is denied. +type ParentIsObject GenericError + +func (e ParentIsObject) Error() string { + return "Parent is object " + e.Bucket + SlashSeparator + path.Dir(e.Object) +} + +// BucketExists bucket exists. +type BucketExists GenericError + +func (e BucketExists) Error() string { + return "Bucket exists: " + e.Bucket +} + +// UnsupportedDelimiter - unsupported delimiter. +type UnsupportedDelimiter struct { + Delimiter string +} + +func (e UnsupportedDelimiter) Error() string { + return fmt.Sprintf("delimiter '%s' is not supported. Only '/' is supported", e.Delimiter) +} + +// InvalidUploadIDKeyCombination - invalid upload id and key marker combination. +type InvalidUploadIDKeyCombination struct { + UploadIDMarker, KeyMarker string +} + +func (e InvalidUploadIDKeyCombination) Error() string { + return fmt.Sprintf("Invalid combination of uploadID marker '%s' and marker '%s'", e.UploadIDMarker, e.KeyMarker) +} + +// InvalidMarkerPrefixCombination - invalid marker and prefix combination. +type InvalidMarkerPrefixCombination struct { + Marker, Prefix string +} + +func (e InvalidMarkerPrefixCombination) Error() string { + return fmt.Sprintf("Invalid combination of marker '%s' and prefix '%s'", e.Marker, e.Prefix) +} + +// BucketPolicyNotFound - no bucket policy found. +type BucketPolicyNotFound GenericError + +func (e BucketPolicyNotFound) Error() string { + return "No bucket policy configuration found for bucket: " + e.Bucket +} + +// BucketLifecycleNotFound - no bucket lifecycle found. +type BucketLifecycleNotFound GenericError + +func (e BucketLifecycleNotFound) Error() string { + return "No bucket lifecycle configuration found for bucket : " + e.Bucket +} + +// BucketSSEConfigNotFound - no bucket encryption found +type BucketSSEConfigNotFound GenericError + +func (e BucketSSEConfigNotFound) Error() string { + return "No bucket encryption configuration found for bucket: " + e.Bucket +} + +// BucketTaggingNotFound - no bucket tags found +type BucketTaggingNotFound GenericError + +func (e BucketTaggingNotFound) Error() string { + return "No bucket tags found for bucket: " + e.Bucket +} + +// BucketObjectLockConfigNotFound - no bucket object lock config found +type BucketObjectLockConfigNotFound GenericError + +func (e BucketObjectLockConfigNotFound) Error() string { + return "No bucket object lock configuration found for bucket: " + e.Bucket +} + +// BucketQuotaConfigNotFound - no bucket quota config found. +type BucketQuotaConfigNotFound GenericError + +func (e BucketQuotaConfigNotFound) Error() string { + return "No quota config found for bucket : " + e.Bucket +} + +// BucketQuotaExceeded - bucket quota exceeded. +type BucketQuotaExceeded GenericError + +func (e BucketQuotaExceeded) Error() string { + return "Bucket quota exceeded for bucket: " + e.Bucket +} + +// Bucket related errors. + +// BucketNameInvalid - bucketname provided is invalid. +type BucketNameInvalid GenericError + +// Error returns string an error formatted as the given text. +func (e BucketNameInvalid) Error() string { + return "Bucket name invalid: " + e.Bucket +} + +// Object related errors. + +// ObjectNameInvalid - object name provided is invalid. +type ObjectNameInvalid GenericError + +// ObjectNameTooLong - object name too long. +type ObjectNameTooLong GenericError + +// ObjectNamePrefixAsSlash - object name has a slash as prefix. +type ObjectNamePrefixAsSlash GenericError + +// Error returns string an error formatted as the given text. +func (e ObjectNameInvalid) Error() string { + return "Object name invalid: " + e.Bucket + "#" + e.Object +} + +// Error returns string an error formatted as the given text. +func (e ObjectNameTooLong) Error() string { + return "Object name too long: " + e.Bucket + "#" + e.Object +} + +// Error returns string an error formatted as the given text. +func (e ObjectNamePrefixAsSlash) Error() string { + return "Object name contains forward slash as pefix: " + e.Bucket + "#" + e.Object +} + +// AllAccessDisabled All access to this object has been disabled +type AllAccessDisabled GenericError + +// Error returns string an error formatted as the given text. +func (e AllAccessDisabled) Error() string { + return "All access to this object has been disabled" +} + +// IncompleteBody You did not provide the number of bytes specified by the Content-Length HTTP header. +type IncompleteBody GenericError + +// Error returns string an error formatted as the given text. +func (e IncompleteBody) Error() string { + return e.Bucket + "#" + e.Object + "has incomplete body" +} + +// InvalidRange - invalid range typed error. +type InvalidRange struct { + OffsetBegin int64 + OffsetEnd int64 + ResourceSize int64 +} + +func (e InvalidRange) Error() string { + return fmt.Sprintf("The requested range \"bytes %d-%d/%d\" is not satisfiable.", e.OffsetBegin, e.OffsetEnd, e.ResourceSize) +} + +// ObjectTooLarge error returned when the size of the object > max object size allowed (5G) per request. +type ObjectTooLarge GenericError + +func (e ObjectTooLarge) Error() string { + return "size of the object greater than what is allowed(5G)" +} + +// ObjectTooSmall error returned when the size of the object < what is expected. +type ObjectTooSmall GenericError + +func (e ObjectTooSmall) Error() string { + return "size of the object less than what is expected" +} + +// OperationTimedOut - a timeout occurred. +type OperationTimedOut struct { +} + +func (e OperationTimedOut) Error() string { + return "Operation timed out" +} + +// Multipart related errors. + +// MalformedUploadID malformed upload id. +type MalformedUploadID struct { + UploadID string +} + +func (e MalformedUploadID) Error() string { + return "Malformed upload id " + e.UploadID +} + +// InvalidUploadID invalid upload id. +type InvalidUploadID struct { + Bucket string + Object string + UploadID string +} + +func (e InvalidUploadID) Error() string { + return "Invalid upload id " + e.UploadID +} + +// InvalidPart One or more of the specified parts could not be found +type InvalidPart struct { + PartNumber int + ExpETag string + GotETag string +} + +func (e InvalidPart) Error() string { + return fmt.Sprintf("Specified part could not be found. PartNumber %d, Expected %s, got %s", + e.PartNumber, e.ExpETag, e.GotETag) +} + +// PartTooSmall - error if part size is less than 5MB. +type PartTooSmall struct { + PartSize int64 + PartNumber int + PartETag string +} + +func (e PartTooSmall) Error() string { + return fmt.Sprintf("Part size for %d should be at least 5MB", e.PartNumber) +} + +// PartTooBig returned if size of part is bigger than the allowed limit. +type PartTooBig struct{} + +func (e PartTooBig) Error() string { + return "Part size bigger than the allowed limit" +} + +// InvalidETag error returned when the etag has changed on disk +type InvalidETag struct{} + +func (e InvalidETag) Error() string { + return "etag of the object has changed" +} + +// NotImplemented If a feature is not implemented +type NotImplemented struct{} + +func (e NotImplemented) Error() string { + return "Not Implemented" +} + +// UnsupportedMetadata - unsupported metadata +type UnsupportedMetadata struct{} + +func (e UnsupportedMetadata) Error() string { + return "Unsupported headers in Metadata" +} + +// BackendDown is returned for network errors or if the gateway's backend is down. +type BackendDown struct{} + +func (e BackendDown) Error() string { + return "Backend down" +} + +// isErrBucketNotFound - Check if error type is BucketNotFound. +func isErrBucketNotFound(err error) bool { + var bkNotFound BucketNotFound + return errors.As(err, &bkNotFound) +} + +// isErrObjectNotFound - Check if error type is ObjectNotFound. +func isErrObjectNotFound(err error) bool { + var objNotFound ObjectNotFound + return errors.As(err, &objNotFound) +} + +// PreConditionFailed - Check if copy precondition failed +type PreConditionFailed struct{} + +func (e PreConditionFailed) Error() string { + return "At least one of the pre-conditions you specified did not hold" +} + +func isErrPreconditionFailed(err error) bool { + _, ok := err.(PreConditionFailed) + return ok +} diff --git a/neofs/api/reqinfo.go b/neofs/api/reqinfo.go new file mode 100644 index 00000000..cc4dc2e0 --- /dev/null +++ b/neofs/api/reqinfo.go @@ -0,0 +1,112 @@ +package api + +import ( + "context" + "sync" +) + +type ( + // KeyVal - appended to ReqInfo.Tags + KeyVal struct { + Key string + Val string + } + + // ReqInfo stores the request info. + ReqInfo struct { + sync.RWMutex + RemoteHost string // Client Host/IP + Host string // Node Host/IP + UserAgent string // User Agent + DeploymentID string // x-minio-deployment-id + RequestID string // x-amz-request-id + API string // API name - GetObject PutObject NewMultipartUpload etc. + BucketName string // Bucket name + ObjectName string // Object name + tags []KeyVal // Any additional info not accommodated by above fields + } +) + +// Key used for Get/SetReqInfo +type contextKeyType string + +const ctxRequestInfo = contextKeyType("NeoFS-S3-Gate") + +// NewReqInfo : +func NewReqInfo(remoteHost, userAgent, deploymentID, requestID, api, bucket, object string) *ReqInfo { + req := ReqInfo{} + req.RemoteHost = remoteHost + req.UserAgent = userAgent + req.API = api + req.DeploymentID = deploymentID + req.RequestID = requestID + req.BucketName = bucket + req.ObjectName = object + return &req +} + +// AppendTags - appends key/val to ReqInfo.tags +func (r *ReqInfo) AppendTags(key string, val string) *ReqInfo { + if r == nil { + return nil + } + r.Lock() + defer r.Unlock() + r.tags = append(r.tags, KeyVal{key, val}) + return r +} + +// SetTags - sets key/val to ReqInfo.tags +func (r *ReqInfo) SetTags(key string, val string) *ReqInfo { + if r == nil { + return nil + } + r.Lock() + defer r.Unlock() + // Search of tag key already exists in tags + var updated bool + for _, tag := range r.tags { + if tag.Key == key { + tag.Val = val + updated = true + break + } + } + if !updated { + // Append to the end of tags list + r.tags = append(r.tags, KeyVal{key, val}) + } + return r +} + +// GetTags - returns the user defined tags +func (r *ReqInfo) GetTags() []KeyVal { + if r == nil { + return nil + } + r.RLock() + defer r.RUnlock() + return append([]KeyVal(nil), r.tags...) +} + +// SetReqInfo sets ReqInfo in the context. +func SetReqInfo(ctx context.Context, req *ReqInfo) context.Context { + if ctx == nil { + return nil + } + return context.WithValue(ctx, ctxRequestInfo, req) +} + +// GetReqInfo returns ReqInfo if set. +func GetReqInfo(ctx context.Context) *ReqInfo { + if ctx != nil { + r, ok := ctx.Value(ctxRequestInfo).(*ReqInfo) + if ok { + return r + } + r = &ReqInfo{} + SetReqInfo(ctx, r) + return r + } + return nil +} diff --git a/neofs/api/response.go b/neofs/api/response.go new file mode 100644 index 00000000..b99af348 --- /dev/null +++ b/neofs/api/response.go @@ -0,0 +1,130 @@ +package api + +import ( + "bytes" + "context" + "encoding/xml" + "fmt" + "net/http" + "net/url" + "strconv" + + "github.com/google/uuid" + "github.com/minio/minio/misc" +) + +type ( + // APIErrorResponse - error response format + ErrorResponse struct { + XMLName xml.Name `xml:"Error" json:"-"` + Code string + Message string + Key string `xml:"Key,omitempty" json:"Key,omitempty"` + BucketName string `xml:"BucketName,omitempty" json:"BucketName,omitempty"` + Resource string + Region string `xml:"Region,omitempty" json:"Region,omitempty"` + RequestID string `xml:"RequestId" json:"RequestId"` + HostID string `xml:"HostId" json:"HostId"` + } + + // APIError structure + Error struct { + Code string + Description string + HTTPStatusCode int + } +) + +const ( + hdrServerInfo = "Server" + hdrAcceptRanges = "Accept-Ranges" + hdrContentType = "Content-Type" + hdrContentLength = "Content-Length" + hdrRetryAfter = "Retry-After" + + hdrAmzCopySource = "X-Amz-Copy-Source" + + // Response request id. + hdrAmzRequestID = "x-amz-request-id" + + // hdrSSE is the general AWS SSE HTTP header key. + hdrSSE = "X-Amz-Server-Side-Encryption" + + // hdrSSECustomerKey is the HTTP header key referencing the + // SSE-C client-provided key.. + hdrSSECustomerKey = hdrSSE + "-Customer-Key" + + // hdrSSECopyKey is the HTTP header key referencing the SSE-C + // client-provided key for SSE-C copy requests. + hdrSSECopyKey = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key" +) + +var deploymentID, _ = uuid.NewRandom() + +// WriteErrorResponse writes error headers +func WriteErrorResponse(ctx context.Context, w http.ResponseWriter, err Error, reqURL *url.URL) { + switch err.Code { + case "SlowDown", "XNeoFSServerNotInitialized", "XNeoFSReadQuorum", "XNeoFSWriteQuorum": + // Set retry-after header to indicate user-agents to retry request after 120secs. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + w.Header().Set(hdrRetryAfter, "120") + case "AccessDenied": + // TODO process when the request is from browser and also if browser + } + + // Generate error response. + errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, + w.Header().Get(hdrAmzRequestID), deploymentID.String()) + encodedErrorResponse := encodeResponse(errorResponse) + writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML) +} + +// If none of the http routes match respond with appropriate errors +func errorResponseHandler(w http.ResponseWriter, r *http.Request) { + desc := fmt.Sprintf("Unknown API request at %s", r.URL.Path) + WriteErrorResponse(r.Context(), w, Error{ + Code: "XMinioUnknownAPIRequest", + Description: desc, + HTTPStatusCode: http.StatusBadRequest, + }, r.URL) +} + +// Write http common headers +func setCommonHeaders(w http.ResponseWriter) { + w.Header().Set(hdrServerInfo, "NeoFS-S3-Gate/"+misc.Version) + w.Header().Set(hdrAcceptRanges, "bytes") + + // Remove sensitive information + removeSensitiveHeaders(w.Header()) +} + +// removeSensitiveHeaders removes confidential encryption +// information - e.g. the SSE-C key - from the HTTP headers. +// It has the same semantics as RemoveSensitiveEntires. +func removeSensitiveHeaders(h http.Header) { + h.Del(hdrSSECustomerKey) + h.Del(hdrSSECopyKey) +} + +func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) { + setCommonHeaders(w) + if mType != mimeNone { + w.Header().Set(hdrContentType, string(mType)) + } + w.Header().Set(hdrContentLength, strconv.Itoa(len(response))) + w.WriteHeader(statusCode) + if response != nil { + _, _ = w.Write(response) + w.(http.Flusher).Flush() + } +} + +// Encodes the response headers into XML format. +func encodeResponse(response interface{}) []byte { + var bytesBuffer bytes.Buffer + bytesBuffer.WriteString(xml.Header) + _ = xml. + NewEncoder(&bytesBuffer). + Encode(response) + return bytesBuffer.Bytes() +} diff --git a/neofs/api/router.go b/neofs/api/router.go new file mode 100644 index 00000000..35d14310 --- /dev/null +++ b/neofs/api/router.go @@ -0,0 +1,301 @@ +package api + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/minio/minio/neofs/metrics" +) + +type ( + Handler interface { + HeadObjectHandler(http.ResponseWriter, *http.Request) + CopyObjectPartHandler(http.ResponseWriter, *http.Request) + PutObjectPartHandler(http.ResponseWriter, *http.Request) + ListObjectPartsHandler(http.ResponseWriter, *http.Request) + CompleteMultipartUploadHandler(http.ResponseWriter, *http.Request) + NewMultipartUploadHandler(http.ResponseWriter, *http.Request) + AbortMultipartUploadHandler(http.ResponseWriter, *http.Request) + GetObjectACLHandler(http.ResponseWriter, *http.Request) + PutObjectACLHandler(http.ResponseWriter, *http.Request) + GetObjectTaggingHandler(http.ResponseWriter, *http.Request) + PutObjectTaggingHandler(http.ResponseWriter, *http.Request) + DeleteObjectTaggingHandler(http.ResponseWriter, *http.Request) + SelectObjectContentHandler(http.ResponseWriter, *http.Request) + GetObjectRetentionHandler(http.ResponseWriter, *http.Request) + GetObjectLegalHoldHandler(http.ResponseWriter, *http.Request) + GetObjectHandler(http.ResponseWriter, *http.Request) + CopyObjectHandler(http.ResponseWriter, *http.Request) + PutObjectRetentionHandler(http.ResponseWriter, *http.Request) + PutObjectLegalHoldHandler(http.ResponseWriter, *http.Request) + PutObjectHandler(http.ResponseWriter, *http.Request) + DeleteObjectHandler(http.ResponseWriter, *http.Request) + GetBucketLocationHandler(http.ResponseWriter, *http.Request) + GetBucketPolicyHandler(http.ResponseWriter, *http.Request) + GetBucketLifecycleHandler(http.ResponseWriter, *http.Request) + GetBucketEncryptionHandler(http.ResponseWriter, *http.Request) + GetBucketACLHandler(http.ResponseWriter, *http.Request) + PutBucketACLHandler(http.ResponseWriter, *http.Request) + GetBucketCorsHandler(http.ResponseWriter, *http.Request) + GetBucketWebsiteHandler(http.ResponseWriter, *http.Request) + GetBucketAccelerateHandler(http.ResponseWriter, *http.Request) + GetBucketRequestPaymentHandler(http.ResponseWriter, *http.Request) + GetBucketLoggingHandler(http.ResponseWriter, *http.Request) + GetBucketReplicationHandler(http.ResponseWriter, *http.Request) + GetBucketTaggingHandler(http.ResponseWriter, *http.Request) + DeleteBucketWebsiteHandler(http.ResponseWriter, *http.Request) + DeleteBucketTaggingHandler(http.ResponseWriter, *http.Request) + GetBucketObjectLockConfigHandler(http.ResponseWriter, *http.Request) + GetBucketVersioningHandler(http.ResponseWriter, *http.Request) + GetBucketNotificationHandler(http.ResponseWriter, *http.Request) + ListenBucketNotificationHandler(http.ResponseWriter, *http.Request) + ListMultipartUploadsHandler(http.ResponseWriter, *http.Request) + ListObjectsV2MHandler(http.ResponseWriter, *http.Request) + ListObjectsV2Handler(http.ResponseWriter, *http.Request) + ListBucketObjectVersionsHandler(http.ResponseWriter, *http.Request) + ListObjectsV1Handler(http.ResponseWriter, *http.Request) + PutBucketLifecycleHandler(http.ResponseWriter, *http.Request) + PutBucketEncryptionHandler(http.ResponseWriter, *http.Request) + PutBucketPolicyHandler(http.ResponseWriter, *http.Request) + PutBucketObjectLockConfigHandler(http.ResponseWriter, *http.Request) + PutBucketTaggingHandler(http.ResponseWriter, *http.Request) + PutBucketVersioningHandler(http.ResponseWriter, *http.Request) + PutBucketNotificationHandler(http.ResponseWriter, *http.Request) + PutBucketHandler(http.ResponseWriter, *http.Request) + HeadBucketHandler(http.ResponseWriter, *http.Request) + PostPolicyBucketHandler(http.ResponseWriter, *http.Request) + DeleteMultipleObjectsHandler(http.ResponseWriter, *http.Request) + DeleteBucketPolicyHandler(http.ResponseWriter, *http.Request) + DeleteBucketLifecycleHandler(http.ResponseWriter, *http.Request) + DeleteBucketEncryptionHandler(http.ResponseWriter, *http.Request) + DeleteBucketHandler(http.ResponseWriter, *http.Request) + ListBucketsHandler(http.ResponseWriter, *http.Request) + } + + // mimeType represents various MIME type used API responses. + mimeType string +) + +const ( + + // SlashSeparator - slash separator. + SlashSeparator = "/" + + // Means no response type. + mimeNone mimeType = "" + // Means response type is JSON. + // mimeJSON mimeType = "application/json" + // Means response type is XML. + mimeXML mimeType = "application/xml" +) + +func Attach(r *mux.Router, m MaxClients, h Handler) { + api := r.PathPrefix(SlashSeparator).Subrouter() + + bucket := api.PathPrefix("/{bucket}").Subrouter() + + // Object operations + // HeadObject + bucket.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("headobject", h.HeadObjectHandler))) + // CopyObjectPart + bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(hdrAmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(m.Handle(metrics.APIStats("copyobjectpart", h.CopyObjectPartHandler))).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") + // PutObjectPart + bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("putobjectpart", h.PutObjectPartHandler))).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") + // ListObjectParts + bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("listobjectparts", h.ListObjectPartsHandler))).Queries("uploadId", "{uploadId:.*}") + // CompleteMultipartUpload + bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("completemutipartupload", h.CompleteMultipartUploadHandler))).Queries("uploadId", "{uploadId:.*}") + // NewMultipartUpload + bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("newmultipartupload", h.NewMultipartUploadHandler))).Queries("uploads", "") + // AbortMultipartUpload + bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("abortmultipartupload", h.AbortMultipartUploadHandler))).Queries("uploadId", "{uploadId:.*}") + // GetObjectACL - this is a dummy call. + bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("getobjectacl", h.GetObjectACLHandler))).Queries("acl", "") + // PutObjectACL - this is a dummy call. + bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("putobjectacl", h.PutObjectACLHandler))).Queries("acl", "") + // GetObjectTagging + bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("getobjecttagging", h.GetObjectTaggingHandler))).Queries("tagging", "") + // PutObjectTagging + bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("putobjecttagging", h.PutObjectTaggingHandler))).Queries("tagging", "") + // DeleteObjectTagging + bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("deleteobjecttagging", h.DeleteObjectTaggingHandler))).Queries("tagging", "") + // SelectObjectContent + bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("selectobjectcontent", h.SelectObjectContentHandler))).Queries("select", "").Queries("select-type", "2") + // GetObjectRetention + bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("getobjectretention", h.GetObjectRetentionHandler))).Queries("retention", "") + // GetObjectLegalHold + bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("getobjectlegalhold", h.GetObjectLegalHoldHandler))).Queries("legal-hold", "") + // GetObject + bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("getobject", h.GetObjectHandler))) + // CopyObject + bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(hdrAmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(m.Handle(metrics.APIStats("copyobject", h.CopyObjectHandler))) + // PutObjectRetention + bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("putobjectretention", h.PutObjectRetentionHandler))).Queries("retention", "") + // PutObjectLegalHold + bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("putobjectlegalhold", h.PutObjectLegalHoldHandler))).Queries("legal-hold", "") + + // PutObject + bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("putobject", h.PutObjectHandler))) + // DeleteObject + bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc( + m.Handle(metrics.APIStats("deleteobject", h.DeleteObjectHandler))) + + // Bucket operations + // GetBucketLocation + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketlocation", h.GetBucketLocationHandler))).Queries("location", "") + // GetBucketPolicy + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketpolicy", h.GetBucketPolicyHandler))).Queries("policy", "") + // GetBucketLifecycle + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketlifecycle", h.GetBucketLifecycleHandler))).Queries("lifecycle", "") + // GetBucketEncryption + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketencryption", h.GetBucketEncryptionHandler))).Queries("encryption", "") + + // Dummy Bucket Calls + // GetBucketACL -- this is a dummy call. + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketacl", h.GetBucketACLHandler))).Queries("acl", "") + // PutBucketACL -- this is a dummy call. + bucket.Methods(http.MethodPut).HandlerFunc( + m.Handle(metrics.APIStats("putbucketacl", h.PutBucketACLHandler))).Queries("acl", "") + // GetBucketCors - this is a dummy call. + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketcors", h.GetBucketCorsHandler))).Queries("cors", "") + // GetBucketWebsiteHandler - this is a dummy call. + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketwebsite", h.GetBucketWebsiteHandler))).Queries("website", "") + // GetBucketAccelerateHandler - this is a dummy call. + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketaccelerate", h.GetBucketAccelerateHandler))).Queries("accelerate", "") + // GetBucketRequestPaymentHandler - this is a dummy call. + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketrequestpayment", h.GetBucketRequestPaymentHandler))).Queries("requestPayment", "") + // GetBucketLoggingHandler - this is a dummy call. + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketlogging", h.GetBucketLoggingHandler))).Queries("logging", "") + // GetBucketLifecycleHandler - this is a dummy call. + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketlifecycle", h.GetBucketLifecycleHandler))).Queries("lifecycle", "") + // GetBucketReplicationHandler - this is a dummy call. + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketreplication", h.GetBucketReplicationHandler))).Queries("replication", "") + // GetBucketTaggingHandler + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbuckettagging", h.GetBucketTaggingHandler))).Queries("tagging", "") + // DeleteBucketWebsiteHandler + bucket.Methods(http.MethodDelete).HandlerFunc( + m.Handle(metrics.APIStats("deletebucketwebsite", h.DeleteBucketWebsiteHandler))).Queries("website", "") + // DeleteBucketTaggingHandler + bucket.Methods(http.MethodDelete).HandlerFunc( + m.Handle(metrics.APIStats("deletebuckettagging", h.DeleteBucketTaggingHandler))).Queries("tagging", "") + + // GetBucketObjectLockConfig + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketobjectlockconfiguration", h.GetBucketObjectLockConfigHandler))).Queries("object-lock", "") + // GetBucketVersioning + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketversioning", h.GetBucketVersioningHandler))).Queries("versioning", "") + // GetBucketNotification + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("getbucketnotification", h.GetBucketNotificationHandler))).Queries("notification", "") + // ListenBucketNotification + bucket.Methods(http.MethodGet).HandlerFunc(metrics.APIStats("listenbucketnotification", h.ListenBucketNotificationHandler)).Queries("events", "{events:.*}") + // ListMultipartUploads + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("listmultipartuploads", h.ListMultipartUploadsHandler))).Queries("uploads", "") + // ListObjectsV2M + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("listobjectsv2M", h.ListObjectsV2MHandler))).Queries("list-type", "2", "metadata", "true") + // ListObjectsV2 + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("listobjectsv2", h.ListObjectsV2Handler))).Queries("list-type", "2") + // ListBucketVersions + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("listbucketversions", h.ListBucketObjectVersionsHandler))).Queries("versions", "") + // ListObjectsV1 (Legacy) + bucket.Methods(http.MethodGet).HandlerFunc( + m.Handle(metrics.APIStats("listobjectsv1", h.ListObjectsV1Handler))) + // PutBucketLifecycle + bucket.Methods(http.MethodPut).HandlerFunc( + m.Handle(metrics.APIStats("putbucketlifecycle", h.PutBucketLifecycleHandler))).Queries("lifecycle", "") + // PutBucketEncryption + bucket.Methods(http.MethodPut).HandlerFunc( + m.Handle(metrics.APIStats("putbucketencryption", h.PutBucketEncryptionHandler))).Queries("encryption", "") + + // PutBucketPolicy + bucket.Methods(http.MethodPut).HandlerFunc( + m.Handle(metrics.APIStats("putbucketpolicy", h.PutBucketPolicyHandler))).Queries("policy", "") + + // PutBucketObjectLockConfig + bucket.Methods(http.MethodPut).HandlerFunc( + m.Handle(metrics.APIStats("putbucketobjectlockconfig", h.PutBucketObjectLockConfigHandler))).Queries("object-lock", "") + // PutBucketTaggingHandler + bucket.Methods(http.MethodPut).HandlerFunc( + m.Handle(metrics.APIStats("putbuckettagging", h.PutBucketTaggingHandler))).Queries("tagging", "") + // PutBucketVersioning + bucket.Methods(http.MethodPut).HandlerFunc( + m.Handle(metrics.APIStats("putbucketversioning", h.PutBucketVersioningHandler))).Queries("versioning", "") + // PutBucketNotification + bucket.Methods(http.MethodPut).HandlerFunc( + m.Handle(metrics.APIStats("putbucketnotification", h.PutBucketNotificationHandler))).Queries("notification", "") + // PutBucket + bucket.Methods(http.MethodPut).HandlerFunc( + m.Handle(metrics.APIStats("putbucket", h.PutBucketHandler))) + // HeadBucket + bucket.Methods(http.MethodHead).HandlerFunc( + m.Handle(metrics.APIStats("headbucket", h.HeadBucketHandler))) + // PostPolicy + bucket.Methods(http.MethodPost).HeadersRegexp(hdrContentType, "multipart/form-data*").HandlerFunc( + m.Handle(metrics.APIStats("postpolicybucket", h.PostPolicyBucketHandler))) + // DeleteMultipleObjects + bucket.Methods(http.MethodPost).HandlerFunc( + m.Handle(metrics.APIStats("deletemultipleobjects", h.DeleteMultipleObjectsHandler))).Queries("delete", "") + // DeleteBucketPolicy + bucket.Methods(http.MethodDelete).HandlerFunc( + m.Handle(metrics.APIStats("deletebucketpolicy", h.DeleteBucketPolicyHandler))).Queries("policy", "") + // DeleteBucketLifecycle + bucket.Methods(http.MethodDelete).HandlerFunc( + m.Handle(metrics.APIStats("deletebucketlifecycle", h.DeleteBucketLifecycleHandler))).Queries("lifecycle", "") + // DeleteBucketEncryption + bucket.Methods(http.MethodDelete).HandlerFunc( + m.Handle(metrics.APIStats("deletebucketencryption", h.DeleteBucketEncryptionHandler))).Queries("encryption", "") + // DeleteBucket + bucket.Methods(http.MethodDelete).HandlerFunc( + m.Handle(metrics.APIStats("deletebucket", h.DeleteBucketHandler))) + + // Root operation + + // ListBuckets + api.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc( + m.Handle(metrics.APIStats("listbuckets", h.ListBucketsHandler))) + + // S3 browser with signature v4 adds '//' for ListBuckets request, so rather + // than failing with UnknownAPIRequest we simply handle it for now. + api.Methods(http.MethodGet).Path(SlashSeparator + SlashSeparator).HandlerFunc( + m.Handle(metrics.APIStats("listbuckets", h.ListBucketsHandler))) + + // If none of the routes match add default error handler routes + api.NotFoundHandler = metrics.APIStats("notfound", errorResponseHandler) + api.MethodNotAllowedHandler = metrics.APIStats("methodnotallowed", errorResponseHandler) +} diff --git a/neofs/api/storage-errors.go b/neofs/api/storage-errors.go new file mode 100644 index 00000000..c72b9238 --- /dev/null +++ b/neofs/api/storage-errors.go @@ -0,0 +1,93 @@ +package api + +// errUnexpected - unexpected error, requires manual intervention. +var errUnexpected = StorageErr("Unexpected error, please report this issue at https://github.com/minio/minio/issues") + +// errCorruptedFormat - corrupted backend format. +var errCorruptedFormat = StorageErr("corrupted backend format, please join https://slack.min.io for assistance") + +// errUnformattedDisk - unformatted disk found. +var errUnformattedDisk = StorageErr("unformatted disk found") + +// errUnsupporteDisk - when disk does not support O_DIRECT flag. +var errUnsupportedDisk = StorageErr("disk does not support O_DIRECT") + +// errDiskFull - cannot create volume or files when disk is full. +var errDiskFull = StorageErr("disk path full") + +// errDiskNotFound - cannot find the underlying configured disk anymore. +var errDiskNotFound = StorageErr("disk not found") + +// errFaultyRemoteDisk - remote disk is faulty. +var errFaultyRemoteDisk = StorageErr("remote disk is faulty") + +// errFaultyDisk - disk is faulty. +var errFaultyDisk = StorageErr("disk is faulty") + +// errDiskAccessDenied - we don't have write permissions on disk. +var errDiskAccessDenied = StorageErr("disk access denied") + +// errFileNotFound - cannot find the file. +var errFileNotFound = StorageErr("file not found") + +// errTooManyOpenFiles - too many open files. +var errTooManyOpenFiles = StorageErr("too many open files") + +// errFileNameTooLong - given file name is too long than supported length. +var errFileNameTooLong = StorageErr("file name too long") + +// errVolumeExists - cannot create same volume again. +var errVolumeExists = StorageErr("volume already exists") + +// errIsNotRegular - not of regular file type. +var errIsNotRegular = StorageErr("not of regular file type") + +// errVolumeNotFound - cannot find the volume. +var errVolumeNotFound = StorageErr("volume not found") + +// errVolumeNotEmpty - volume not empty. +var errVolumeNotEmpty = StorageErr("volume is not empty") + +// errVolumeAccessDenied - cannot access volume, insufficient permissions. +var errVolumeAccessDenied = StorageErr("volume access denied") + +// errFileAccessDenied - cannot access file, insufficient permissions. +var errFileAccessDenied = StorageErr("file access denied") + +// errFileCorrupt - file has an unexpected size, or is not readable +var errFileCorrupt = StorageErr("file is corrupted") + +// errFileParentIsFile - cannot have overlapping objects, parent is already a file. +var errFileParentIsFile = StorageErr("parent is a file") + +// errBitrotHashAlgoInvalid - the algo for bit-rot hash +// verification is empty or invalid. +var errBitrotHashAlgoInvalid = StorageErr("bit-rot hash algorithm is invalid") + +// errCrossDeviceLink - rename across devices not allowed. +var errCrossDeviceLink = StorageErr("Rename across devices not allowed, please fix your backend configuration") + +// errMinDiskSize - cannot create volume or files when disk size is less than threshold. +var errMinDiskSize = StorageErr("The disk size is less than 900MiB threshold") + +// errLessData - returned when less data available than what was requested. +var errLessData = StorageErr("less data available than what was requested") + +// errMoreData = returned when more data was sent by the caller than what it was supposed to. +var errMoreData = StorageErr("more data was sent than what was advertised") + +// StorageErr represents error generated by posix call. +type StorageErr string + +func (h StorageErr) Error() string { + return string(h) +} + +// Collection of basic errors. +var baseErrs = []error{ + errDiskNotFound, + errFaultyDisk, + errFaultyRemoteDisk, +} + +var baseIgnoredErrs = baseErrs diff --git a/neofs/api/typed-errors.go b/neofs/api/typed-errors.go new file mode 100644 index 00000000..45791bd6 --- /dev/null +++ b/neofs/api/typed-errors.go @@ -0,0 +1,107 @@ +package api + +import ( + "github.com/pkg/errors" +) + +// errInvalidArgument means that input argument is invalid. +var errInvalidArgument = errors.New("Invalid arguments specified") + +// errMethodNotAllowed means that method is not allowed. +var errMethodNotAllowed = errors.New("Method not allowed") + +// errSignatureMismatch means signature did not match. +var errSignatureMismatch = errors.New("Signature does not match") + +// used when we deal with data larger than expected +var errSizeUnexpected = errors.New("Data size larger than expected") + +// used when we deal with data with unknown size +var errSizeUnspecified = errors.New("Data size is unspecified") + +// When upload object size is greater than 5G in a single PUT/POST operation. +var errDataTooLarge = errors.New("Object size larger than allowed limit") + +// When upload object size is less than what was expected. +var errDataTooSmall = errors.New("Object size smaller than expected") + +// errServerNotInitialized - server not initialized. +var errServerNotInitialized = errors.New("Server not initialized, please try again") + +// errRPCAPIVersionUnsupported - unsupported rpc API version. +var errRPCAPIVersionUnsupported = errors.New("Unsupported rpc API version") + +// errServerTimeMismatch - server times are too far apart. +var errServerTimeMismatch = errors.New("Server times are too far apart") + +// errInvalidBucketName - bucket name is reserved for MinIO, usually +// returned for 'minio', '.minio.sys', buckets with capital letters. +var errInvalidBucketName = errors.New("The specified bucket is not valid") + +// errInvalidRange - returned when given range value is not valid. +var errInvalidRange = errors.New("Invalid range") + +// errInvalidRangeSource - returned when given range value exceeds +// the source object size. +var errInvalidRangeSource = errors.New("Range specified exceeds source object size") + +// error returned by disks which are to be initialized are waiting for the +// first server to initialize them in distributed set to initialize them. +var errNotFirstDisk = errors.New("Not first disk") + +// error returned by first disk waiting to initialize other servers. +var errFirstDiskWait = errors.New("Waiting on other disks") + +// error returned when a bucket already exists +var errBucketAlreadyExists = errors.New("Your previous request to create the named bucket succeeded and you already own it") + +// error returned for a negative actual size. +var errInvalidDecompressedSize = errors.New("Invalid Decompressed Size") + +// error returned in IAM subsystem when user doesn't exist. +var errNoSuchUser = errors.New("Specified user does not exist") + +// error returned in IAM subsystem when groups doesn't exist. +var errNoSuchGroup = errors.New("Specified group does not exist") + +// error returned in IAM subsystem when a non-empty group needs to be +// deleted. +var errGroupNotEmpty = errors.New("Specified group is not empty - cannot remove it") + +// error returned in IAM subsystem when policy doesn't exist. +var errNoSuchPolicy = errors.New("Specified canned policy does not exist") + +// error returned in IAM subsystem when an external users systems is configured. +var errIAMActionNotAllowed = errors.New("Specified IAM action is not allowed with LDAP configuration") + +// error returned in IAM subsystem when IAM sub-system is still being initialized. +var errIAMNotInitialized = errors.New("IAM sub-system is being initialized, please try again") + +// error returned when access is denied. +var errAccessDenied = errors.New("Do not have enough permissions to access this resource") + +// error returned when object is locked. +var errLockedObject = errors.New("Object is WORM protected and cannot be overwritten or deleted") + +var ( + errInvalidAccessKeyID = errors.New("The access key ID you provided does not exist in our records") + errChangeCredNotAllowed = errors.New("Changing access key and secret key not allowed") + errAuthentication = errors.New("Authentication failed, check your access credentials") + errNoAuthToken = errors.New("JWT token missing") + errIncorrectCreds = errors.New("Current access key or secret key is incorrect") + errPresignedNotAllowed = errors.New("Unable to generate shareable URL due to lack of read permissions") +) + +var ( + // AWS errors for invalid SSE-C requests. + errEncryptedObject = errors.New("The object was stored using a form of SSE") + errInvalidSSEParameters = errors.New("The SSE-C key for key-rotation is not correct") // special access denied + errKMSNotConfigured = errors.New("KMS not configured for a server side encrypted object") + // Additional MinIO errors for SSE-C requests. + errObjectTampered = errors.New("The requested object was modified and may be compromised") + // error returned when invalid encryption parameters are specified + errInvalidEncryptionParameters = errors.New("The encryption parameters are not applicable to this object") +) + +// ErrNoEntriesFound - Indicates no entries were found for the given key (directory) +var ErrNoEntriesFound = errors.New("No entries found for this key") diff --git a/neofs/api/xl-v1-errors.go b/neofs/api/xl-v1-errors.go new file mode 100644 index 00000000..52578040 --- /dev/null +++ b/neofs/api/xl-v1-errors.go @@ -0,0 +1,12 @@ +package api + +import "errors" + +// errXLReadQuorum - did not meet read quorum. +var errXLReadQuorum = errors.New("Read failed. Insufficient number of disks online") + +// errXLWriteQuorum - did not meet write quorum. +var errXLWriteQuorum = errors.New("Write failed. Insufficient number of disks online") + +// errNoHealRequired - returned when healing is attempted on a previously healed disks. +var errNoHealRequired = errors.New("No healing is required")