2020-07-06 09:18:16 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-06-22 13:25:29 +00:00
|
|
|
"encoding/hex"
|
2021-05-26 16:48:27 +00:00
|
|
|
"math"
|
2020-07-12 23:00:47 +00:00
|
|
|
"net"
|
|
|
|
"net/http"
|
2020-07-06 09:18:16 +00:00
|
|
|
|
2021-06-22 14:11:44 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
2021-05-18 11:10:08 +00:00
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api"
|
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api/auth"
|
2021-08-16 09:58:09 +00:00
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api/cache"
|
2021-05-18 11:10:08 +00:00
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api/handler"
|
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
|
2021-06-24 15:21:34 +00:00
|
|
|
"github.com/nspcc-dev/neofs-s3-gw/internal/wallet"
|
2021-05-28 08:46:13 +00:00
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/pkg/pool"
|
2020-07-06 09:18:16 +00:00
|
|
|
"github.com/spf13/viper"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
2021-05-13 20:25:31 +00:00
|
|
|
// App is the main application structure.
|
2020-07-06 09:18:16 +00:00
|
|
|
App struct {
|
2021-05-28 20:48:23 +00:00
|
|
|
pool pool.Pool
|
|
|
|
ctr auth.Center
|
|
|
|
log *zap.Logger
|
|
|
|
cfg *viper.Viper
|
|
|
|
tls *tlsConfig
|
|
|
|
obj layer.Client
|
|
|
|
api api.Handler
|
2020-07-06 09:18:16 +00:00
|
|
|
|
2020-07-22 13:02:32 +00:00
|
|
|
maxClients api.MaxClients
|
|
|
|
|
2020-07-06 09:18:16 +00:00
|
|
|
webDone chan struct{}
|
|
|
|
wrkDone chan struct{}
|
|
|
|
}
|
2020-07-13 15:50:11 +00:00
|
|
|
|
|
|
|
tlsConfig struct {
|
|
|
|
KeyFile string
|
|
|
|
CertFile string
|
|
|
|
}
|
2020-07-06 09:18:16 +00:00
|
|
|
)
|
|
|
|
|
2020-10-13 09:31:23 +00:00
|
|
|
func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App {
|
2020-07-06 09:18:16 +00:00
|
|
|
var (
|
2021-05-28 08:46:13 +00:00
|
|
|
conns pool.Pool
|
2021-06-22 14:11:44 +00:00
|
|
|
key *keys.PrivateKey
|
2020-10-13 09:31:23 +00:00
|
|
|
err error
|
|
|
|
tls *tlsConfig
|
|
|
|
caller api.Handler
|
2020-11-24 07:09:58 +00:00
|
|
|
ctr auth.Center
|
2020-10-13 09:31:23 +00:00
|
|
|
obj layer.Client
|
|
|
|
|
2021-05-26 16:48:27 +00:00
|
|
|
poolPeers = fetchPeers(l, v)
|
2020-10-13 09:31:23 +00:00
|
|
|
|
2020-07-15 20:16:27 +00:00
|
|
|
reBalance = defaultRebalanceTimer
|
2020-07-06 09:18:16 +00:00
|
|
|
conTimeout = defaultConnectTimeout
|
|
|
|
reqTimeout = defaultRequestTimeout
|
2020-07-22 13:02:32 +00:00
|
|
|
|
|
|
|
maxClientsCount = defaultMaxClientsCount
|
|
|
|
maxClientsDeadline = defaultMaxClientsDeadline
|
2020-07-06 09:18:16 +00:00
|
|
|
)
|
2020-07-13 15:50:11 +00:00
|
|
|
|
2020-07-07 11:25:13 +00:00
|
|
|
if v := v.GetDuration(cfgConnectTimeout); v > 0 {
|
2020-07-06 09:18:16 +00:00
|
|
|
conTimeout = v
|
|
|
|
}
|
|
|
|
|
2020-07-07 11:25:13 +00:00
|
|
|
if v := v.GetDuration(cfgRequestTimeout); v > 0 {
|
2020-07-06 09:18:16 +00:00
|
|
|
reqTimeout = v
|
|
|
|
}
|
|
|
|
|
2020-07-22 13:02:32 +00:00
|
|
|
if v := v.GetInt(cfgMaxClientsCount); v > 0 {
|
|
|
|
maxClientsCount = v
|
|
|
|
}
|
|
|
|
|
|
|
|
if v := v.GetDuration(cfgMaxClientsDeadline); v > 0 {
|
|
|
|
maxClientsDeadline = v
|
|
|
|
}
|
|
|
|
|
2020-10-13 09:31:23 +00:00
|
|
|
if v := v.GetDuration(cfgRebalanceTimer); v > 0 {
|
|
|
|
reBalance = v
|
|
|
|
}
|
|
|
|
|
2021-06-24 15:21:34 +00:00
|
|
|
password := wallet.GetPassword(v, cfgWalletPassphrase)
|
|
|
|
if key, err = wallet.GetKeyFromPath(v.GetString(cfgWallet), v.GetString(cfgAddress), password); err != nil {
|
2021-06-22 13:25:29 +00:00
|
|
|
l.Fatal("could not load NeoFS private key", zap.Error(err))
|
2020-10-13 09:31:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if v.IsSet(cfgTLSKeyFile) && v.IsSet(cfgTLSCertFile) {
|
|
|
|
tls = &tlsConfig{
|
|
|
|
KeyFile: v.GetString(cfgTLSKeyFile),
|
|
|
|
CertFile: v.GetString(cfgTLSCertFile),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-24 07:09:58 +00:00
|
|
|
l.Info("using credentials",
|
2021-06-22 14:11:44 +00:00
|
|
|
zap.String("NeoFS", hex.EncodeToString(key.PublicKey().Bytes())))
|
2020-11-24 07:09:58 +00:00
|
|
|
|
2021-05-28 08:46:13 +00:00
|
|
|
opts := &pool.BuilderOptions{
|
2021-06-22 14:11:44 +00:00
|
|
|
Key: &key.PrivateKey,
|
2021-05-26 16:48:27 +00:00
|
|
|
NodeConnectionTimeout: conTimeout,
|
|
|
|
NodeRequestTimeout: reqTimeout,
|
|
|
|
ClientRebalanceInterval: reBalance,
|
|
|
|
SessionExpirationEpoch: math.MaxUint64,
|
2020-07-06 09:18:16 +00:00
|
|
|
}
|
2021-05-28 08:46:13 +00:00
|
|
|
conns, err = poolPeers.Build(ctx, opts)
|
2021-05-26 16:48:27 +00:00
|
|
|
if err != nil {
|
|
|
|
l.Fatal("failed to create connection pool", zap.Error(err))
|
2020-07-06 09:18:16 +00:00
|
|
|
}
|
2020-10-19 01:05:28 +00:00
|
|
|
|
2021-08-16 09:58:09 +00:00
|
|
|
cacheCfg := getCacheOptions(v, l)
|
|
|
|
|
2020-11-24 07:09:58 +00:00
|
|
|
// prepare object layer
|
2021-08-16 09:58:09 +00:00
|
|
|
obj = layer.NewLayer(l, conns, cacheCfg)
|
2020-11-24 07:09:58 +00:00
|
|
|
|
|
|
|
// prepare auth center
|
2021-06-24 15:21:34 +00:00
|
|
|
ctr = auth.New(conns, key)
|
2020-07-06 09:18:16 +00:00
|
|
|
|
2020-08-06 10:50:04 +00:00
|
|
|
if caller, err = handler.New(l, obj); err != nil {
|
|
|
|
l.Fatal("could not initialize API handler", zap.Error(err))
|
2020-07-24 16:10:41 +00:00
|
|
|
}
|
|
|
|
|
2020-07-06 09:18:16 +00:00
|
|
|
return &App{
|
2021-05-28 20:48:23 +00:00
|
|
|
ctr: ctr,
|
|
|
|
pool: conns,
|
|
|
|
log: l,
|
|
|
|
cfg: v,
|
|
|
|
obj: obj,
|
|
|
|
tls: tls,
|
|
|
|
api: caller,
|
2020-07-06 09:18:16 +00:00
|
|
|
|
|
|
|
webDone: make(chan struct{}, 1),
|
|
|
|
wrkDone: make(chan struct{}, 1),
|
|
|
|
|
2020-07-22 13:02:32 +00:00
|
|
|
maxClients: api.NewMaxClientsMiddleware(maxClientsCount, maxClientsDeadline),
|
2020-07-06 09:18:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-13 20:25:31 +00:00
|
|
|
// Wait waits for application to finish.
|
2020-07-12 23:00:47 +00:00
|
|
|
func (a *App) Wait() {
|
2020-07-06 09:18:16 +00:00
|
|
|
a.log.Info("application started")
|
2020-07-12 23:00:47 +00:00
|
|
|
|
2021-05-26 16:48:27 +00:00
|
|
|
<-a.webDone // wait for web-server to be stopped
|
2020-07-12 23:00:47 +00:00
|
|
|
|
|
|
|
a.log.Info("application finished")
|
2020-07-06 09:18:16 +00:00
|
|
|
}
|
|
|
|
|
2021-05-13 20:25:31 +00:00
|
|
|
// Server runs HTTP server to handle S3 API requests.
|
2020-07-06 09:18:16 +00:00
|
|
|
func (a *App) Server(ctx context.Context) {
|
2020-07-12 23:00:47 +00:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
lis net.Listener
|
|
|
|
lic net.ListenConfig
|
2020-07-13 09:51:21 +00:00
|
|
|
srv = new(http.Server)
|
2020-07-12 23:00:47 +00:00
|
|
|
addr = a.cfg.GetString(cfgListenAddress)
|
|
|
|
)
|
|
|
|
|
|
|
|
if lis, err = lic.Listen(ctx, "tcp", addr); err != nil {
|
|
|
|
a.log.Fatal("could not prepare listener",
|
|
|
|
zap.Error(err))
|
|
|
|
}
|
|
|
|
|
2020-07-13 09:51:21 +00:00
|
|
|
router := newS3Router()
|
|
|
|
|
2020-07-12 23:00:47 +00:00
|
|
|
// Attach app-specific routes:
|
2021-05-26 16:48:27 +00:00
|
|
|
// attachHealthy(router, a.cli)
|
2020-07-13 09:51:21 +00:00
|
|
|
attachMetrics(router, a.cfg, a.log)
|
|
|
|
attachProfiler(router, a.cfg, a.log)
|
|
|
|
|
|
|
|
// Attach S3 API:
|
2020-12-10 15:11:45 +00:00
|
|
|
domains := fetchDomains(a.cfg)
|
|
|
|
a.log.Info("fetch domains, prepare to use API",
|
|
|
|
zap.Strings("domains", domains))
|
|
|
|
api.Attach(router, domains, a.maxClients, a.api, a.ctr, a.log)
|
2020-07-13 09:51:21 +00:00
|
|
|
|
|
|
|
// Use mux.Router as http.Handler
|
|
|
|
srv.Handler = router
|
2020-10-23 00:12:37 +00:00
|
|
|
srv.ErrorLog = zap.NewStdLog(a.log)
|
2020-07-12 23:00:47 +00:00
|
|
|
|
|
|
|
go func() {
|
|
|
|
a.log.Info("starting server",
|
|
|
|
zap.String("bind", addr))
|
|
|
|
|
2020-07-13 15:50:11 +00:00
|
|
|
switch a.tls {
|
|
|
|
case nil:
|
|
|
|
if err = srv.Serve(lis); err != nil && err != http.ErrServerClosed {
|
|
|
|
a.log.Fatal("listen and serve",
|
|
|
|
zap.Error(err))
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
a.log.Info("using certificate",
|
|
|
|
zap.String("key", a.tls.KeyFile),
|
|
|
|
zap.String("cert", a.tls.CertFile))
|
2020-07-13 11:23:23 +00:00
|
|
|
|
2020-07-13 15:50:11 +00:00
|
|
|
if err = srv.ServeTLS(lis, a.tls.CertFile, a.tls.KeyFile); err != nil && err != http.ErrServerClosed {
|
|
|
|
a.log.Fatal("listen and serve",
|
|
|
|
zap.Error(err))
|
|
|
|
}
|
2020-07-12 23:00:47 +00:00
|
|
|
}
|
2020-07-06 09:18:16 +00:00
|
|
|
}()
|
2020-07-12 23:00:47 +00:00
|
|
|
|
|
|
|
<-ctx.Done()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultShutdownTimeout)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
a.log.Info("stopping server",
|
|
|
|
zap.Error(srv.Shutdown(ctx)))
|
|
|
|
|
|
|
|
close(a.webDone)
|
2020-07-06 09:18:16 +00:00
|
|
|
}
|
2021-08-16 09:58:09 +00:00
|
|
|
|
|
|
|
func getCacheOptions(v *viper.Viper, l *zap.Logger) *layer.CacheConfig {
|
|
|
|
cacheCfg := layer.CacheConfig{
|
|
|
|
ListObjectsLifetime: layer.DefaultObjectsListCacheLifetime,
|
|
|
|
Size: cache.DefaultObjectsCacheSize,
|
|
|
|
Lifetime: cache.DefaultObjectsCacheLifetime,
|
|
|
|
}
|
|
|
|
|
|
|
|
if v.IsSet(cfgObjectsCacheLifetime) {
|
|
|
|
lifetime := v.GetDuration(cfgObjectsCacheLifetime)
|
|
|
|
if lifetime <= 0 {
|
|
|
|
l.Error("invalid cache lifetime, using default value (in seconds)",
|
|
|
|
zap.Duration("value in config", lifetime),
|
|
|
|
zap.Duration("default", cacheCfg.Lifetime))
|
|
|
|
} else {
|
|
|
|
cacheCfg.Lifetime = lifetime
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if v.IsSet(cfgCacheSize) {
|
|
|
|
size := v.GetInt(cfgCacheSize)
|
|
|
|
if size <= 0 {
|
|
|
|
l.Error("invalid cache size, using default value",
|
|
|
|
zap.Int("value in config", size),
|
|
|
|
zap.Int("default", cacheCfg.Size))
|
|
|
|
} else {
|
|
|
|
cacheCfg.Size = size
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if v.IsSet(cfgListObjectsCacheLifetime) {
|
|
|
|
lifetime := v.GetDuration(cfgListObjectsCacheLifetime)
|
|
|
|
if lifetime <= 0 {
|
|
|
|
l.Error("invalid list objects cache lifetime, using default value (in seconds)",
|
|
|
|
zap.Duration("value in config", lifetime),
|
|
|
|
zap.Duration("default", cacheCfg.ListObjectsLifetime))
|
|
|
|
} else {
|
|
|
|
cacheCfg.ListObjectsLifetime = lifetime
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &cacheCfg
|
|
|
|
}
|