forked from TrueCloudLab/frostfs-s3-gw
NFSSVC-30 Isolate S3 routing from legacy code
This commit is contained in:
parent
1c6da41bee
commit
c6bc8c513b
15 changed files with 3877 additions and 23 deletions
17
auth/errors.go
Normal file
17
auth/errors.go
Normal file
|
@ -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)
|
||||||
|
)
|
|
@ -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)
|
router.Use(uamw)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,9 @@ const (
|
||||||
|
|
||||||
defaultKeepaliveTime = 10 * time.Second
|
defaultKeepaliveTime = 10 * time.Second
|
||||||
defaultKeepaliveTimeout = 10 * time.Second
|
defaultKeepaliveTimeout = 10 * time.Second
|
||||||
|
|
||||||
|
defaultMaxClientsCount = 100
|
||||||
|
defaultMaxClientsDeadline = time.Second * 30
|
||||||
)
|
)
|
||||||
|
|
||||||
const ( // settings
|
const ( // settings
|
||||||
|
@ -69,6 +72,10 @@ const ( // settings
|
||||||
cfgRequestTimeout = "request_timeout"
|
cfgRequestTimeout = "request_timeout"
|
||||||
cfgRebalanceTimer = "rebalance_timer"
|
cfgRebalanceTimer = "rebalance_timer"
|
||||||
|
|
||||||
|
// MaxClients
|
||||||
|
cfgMaxClientsCount = "max_clients_count"
|
||||||
|
cfgMaxClientsDeadline = "max_clients_deadline"
|
||||||
|
|
||||||
// gRPC
|
// gRPC
|
||||||
cfgGRPCVerbose = "verbose"
|
cfgGRPCVerbose = "verbose"
|
||||||
|
|
||||||
|
@ -108,14 +115,16 @@ func fetchAuthCenter(l *zap.Logger, v *viper.Viper) (*auth.Center, error) {
|
||||||
uapk := v.GetString(cfgUserAuthPrivateKey)
|
uapk := v.GetString(cfgUserAuthPrivateKey)
|
||||||
userAuthPrivateKey, err = auth.ReadRSAPrivateKeyFromPEMFile(uapk)
|
userAuthPrivateKey, err = auth.ReadRSAPrivateKeyFromPEMFile(uapk)
|
||||||
if err != nil {
|
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)
|
center, err := auth.NewCenter(l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to create auth center")
|
return nil, errors.Wrap(err, "failed to create auth center")
|
||||||
}
|
}
|
||||||
center.SetUserAuthKeys(userAuthPrivateKey)
|
center.SetUserAuthKeys(userAuthPrivateKey)
|
||||||
center.SetNeoFSKeys(neofsPrivateKey)
|
if err = center.SetNeoFSKeys(neofsPrivateKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return center, nil
|
return center, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,6 +177,9 @@ func newSettings() *viper.Viper {
|
||||||
flags.Duration(cfgConnectTimeout, defaultConnectTimeout, "set gRPC connect timeout")
|
flags.Duration(cfgConnectTimeout, defaultConnectTimeout, "set gRPC connect timeout")
|
||||||
flags.Duration(cfgRebalanceTimer, defaultRebalanceTimer, "set gRPC connection rebalance timer")
|
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")
|
ttl := flags.DurationP(cfgConnectionTTL, "t", defaultTTL, "set gRPC connection time to live")
|
||||||
|
|
||||||
flags.String(cfgListenAddress, "0.0.0.0:8080", "set address to listen")
|
flags.String(cfgListenAddress, "0.0.0.0:8080", "set address to listen")
|
||||||
|
|
|
@ -8,14 +8,17 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/auth"
|
"github.com/minio/minio/auth"
|
||||||
minio "github.com/minio/minio/legacy"
|
"github.com/minio/minio/neofs/api"
|
||||||
"github.com/minio/minio/legacy/config"
|
"github.com/minio/minio/neofs/api/handler"
|
||||||
"github.com/minio/minio/neofs/layer"
|
"github.com/minio/minio/neofs/layer"
|
||||||
"github.com/minio/minio/neofs/metrics"
|
|
||||||
"github.com/minio/minio/neofs/pool"
|
"github.com/minio/minio/neofs/pool"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
|
// should be removed in future
|
||||||
|
"github.com/minio/minio/legacy"
|
||||||
|
"github.com/minio/minio/legacy/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -25,13 +28,16 @@ type (
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
cfg *viper.Viper
|
cfg *viper.Viper
|
||||||
tls *tlsConfig
|
tls *tlsConfig
|
||||||
obj minio.ObjectLayer
|
obj legacy.ObjectLayer
|
||||||
|
api api.Handler
|
||||||
|
|
||||||
conTimeout time.Duration
|
conTimeout time.Duration
|
||||||
reqTimeout time.Duration
|
reqTimeout time.Duration
|
||||||
|
|
||||||
reBalance time.Duration
|
reBalance time.Duration
|
||||||
|
|
||||||
|
maxClients api.MaxClients
|
||||||
|
|
||||||
webDone chan struct{}
|
webDone chan struct{}
|
||||||
wrkDone chan struct{}
|
wrkDone chan struct{}
|
||||||
}
|
}
|
||||||
|
@ -47,10 +53,14 @@ func newApp(l *zap.Logger, v *viper.Viper) *App {
|
||||||
err error
|
err error
|
||||||
cli pool.Pool
|
cli pool.Pool
|
||||||
tls *tlsConfig
|
tls *tlsConfig
|
||||||
obj minio.ObjectLayer
|
caller api.Handler
|
||||||
|
obj legacy.ObjectLayer
|
||||||
reBalance = defaultRebalanceTimer
|
reBalance = defaultRebalanceTimer
|
||||||
conTimeout = defaultConnectTimeout
|
conTimeout = defaultConnectTimeout
|
||||||
reqTimeout = defaultRequestTimeout
|
reqTimeout = defaultRequestTimeout
|
||||||
|
|
||||||
|
maxClientsCount = defaultMaxClientsCount
|
||||||
|
maxClientsDeadline = defaultMaxClientsDeadline
|
||||||
)
|
)
|
||||||
|
|
||||||
center, err := fetchAuthCenter(l, v)
|
center, err := fetchAuthCenter(l, v)
|
||||||
|
@ -60,6 +70,10 @@ func newApp(l *zap.Logger, v *viper.Viper) *App {
|
||||||
uid := center.GetOwnerID()
|
uid := center.GetOwnerID()
|
||||||
wif := center.GetWIFString()
|
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) {
|
if v.IsSet(cfgTLSKeyFile) && v.IsSet(cfgTLSCertFile) {
|
||||||
tls = &tlsConfig{
|
tls = &tlsConfig{
|
||||||
KeyFile: v.GetString(cfgTLSKeyFile),
|
KeyFile: v.GetString(cfgTLSKeyFile),
|
||||||
|
@ -75,6 +89,14 @@ func newApp(l *zap.Logger, v *viper.Viper) *App {
|
||||||
reqTimeout = v
|
reqTimeout = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v := v.GetInt(cfgMaxClientsCount); v > 0 {
|
||||||
|
maxClientsCount = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := v.GetDuration(cfgMaxClientsDeadline); v > 0 {
|
||||||
|
maxClientsDeadline = v
|
||||||
|
}
|
||||||
|
|
||||||
poolConfig := &pool.Config{
|
poolConfig := &pool.Config{
|
||||||
ConnectionTTL: v.GetDuration(cfgConnectionTTL),
|
ConnectionTTL: v.GetDuration(cfgConnectionTTL),
|
||||||
ConnectTimeout: v.GetDuration(cfgConnectTimeout),
|
ConnectTimeout: v.GetDuration(cfgConnectTimeout),
|
||||||
|
@ -133,12 +155,15 @@ func newApp(l *zap.Logger, v *viper.Viper) *App {
|
||||||
cfg: v,
|
cfg: v,
|
||||||
obj: obj,
|
obj: obj,
|
||||||
tls: tls,
|
tls: tls,
|
||||||
|
api: caller,
|
||||||
|
|
||||||
webDone: make(chan struct{}, 1),
|
webDone: make(chan struct{}, 1),
|
||||||
wrkDone: make(chan struct{}, 1),
|
wrkDone: make(chan struct{}, 1),
|
||||||
|
|
||||||
reBalance: reBalance,
|
reBalance: reBalance,
|
||||||
|
|
||||||
|
maxClients: api.NewMaxClientsMiddleware(maxClientsCount, maxClientsDeadline),
|
||||||
|
|
||||||
conTimeout: conTimeout,
|
conTimeout: conTimeout,
|
||||||
reqTimeout: reqTimeout,
|
reqTimeout: reqTimeout,
|
||||||
}
|
}
|
||||||
|
@ -179,23 +204,8 @@ func (a *App) Server(ctx context.Context) {
|
||||||
attachMetrics(router, a.cfg, a.log)
|
attachMetrics(router, a.cfg, a.log)
|
||||||
attachProfiler(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:
|
// Attach S3 API:
|
||||||
r := router.PathPrefix(minio.SlashSeparator).Subrouter()
|
api.Attach(router, a.maxClients, a.api)
|
||||||
r.Use(metrics.Middleware)
|
|
||||||
minio.AttachS3API(r, a.obj, a.log)
|
|
||||||
|
|
||||||
// Use mux.Router as http.Handler
|
// Use mux.Router as http.Handler
|
||||||
srv.Handler = router
|
srv.Handler = router
|
||||||
|
|
63
neofs/api/crypto/errors.go
Normal file
63
neofs/api/crypto/errors.go
Normal file
|
@ -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 <nil>"
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
)
|
1964
neofs/api/errors.go
Normal file
1964
neofs/api/errors.go
Normal file
File diff suppressed because it is too large
Load diff
501
neofs/api/handler/unimplemented.go
Normal file
501
neofs/api/handler/unimplemented.go
Normal file
|
@ -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)
|
||||||
|
}
|
56
neofs/api/max-clients.go
Normal file
56
neofs/api/max-clients.go
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
474
neofs/api/object-errors.go
Normal file
474
neofs/api/object-errors.go
Normal file
|
@ -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
|
||||||
|
}
|
112
neofs/api/reqinfo.go
Normal file
112
neofs/api/reqinfo.go
Normal file
|
@ -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
|
||||||
|
}
|
130
neofs/api/response.go
Normal file
130
neofs/api/response.go
Normal file
|
@ -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()
|
||||||
|
}
|
301
neofs/api/router.go
Normal file
301
neofs/api/router.go
Normal file
|
@ -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)
|
||||||
|
}
|
93
neofs/api/storage-errors.go
Normal file
93
neofs/api/storage-errors.go
Normal file
|
@ -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
|
107
neofs/api/typed-errors.go
Normal file
107
neofs/api/typed-errors.go
Normal file
|
@ -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")
|
12
neofs/api/xl-v1-errors.go
Normal file
12
neofs/api/xl-v1-errors.go
Normal file
|
@ -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")
|
Loading…
Reference in a new issue