frostfs-s3-gw/cmd/s3-gw/app.go

256 lines
5.7 KiB
Go
Raw Normal View History

2020-07-06 09:18:16 +00:00
package main
import (
"context"
"encoding/hex"
"fmt"
"math"
2020-07-12 23:00:47 +00:00
"net"
"net/http"
2020-07-06 09:18:16 +00:00
"github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/nspcc-dev/neofs-s3-gw/api"
"github.com/nspcc-dev/neofs-s3-gw/api/auth"
"github.com/nspcc-dev/neofs-s3-gw/api/handler"
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
"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 (
// App is the main application structure.
2020-07-06 09:18:16 +00:00
App struct {
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
maxClients api.MaxClients
2020-07-06 09:18:16 +00:00
webDone chan struct{}
wrkDone chan struct{}
}
tlsConfig struct {
KeyFile string
CertFile string
}
2020-07-06 09:18:16 +00:00
)
func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App {
2020-07-06 09:18:16 +00:00
var (
conns pool.Pool
key *keys.PrivateKey
err error
tls *tlsConfig
caller api.Handler
ctr auth.Center
obj layer.Client
poolPeers = fetchPeers(l, v)
reBalance = defaultRebalanceTimer
2020-07-06 09:18:16 +00:00
conTimeout = defaultConnectTimeout
reqTimeout = defaultRequestTimeout
maxClientsCount = defaultMaxClientsCount
maxClientsDeadline = defaultMaxClientsDeadline
2020-07-06 09:18:16 +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
}
if v := v.GetInt(cfgMaxClientsCount); v > 0 {
maxClientsCount = v
}
if v := v.GetDuration(cfgMaxClientsDeadline); v > 0 {
maxClientsDeadline = v
}
if v := v.GetDuration(cfgRebalanceTimer); v > 0 {
reBalance = v
}
var password *string
if v.IsSet(cfgWalletPassphrase) {
pwd := v.GetString(cfgWalletPassphrase)
password = &pwd
}
if key, err = getKeyFromWallet(v.GetString(cfgWallet), v.GetString(cfgAddress), password); err != nil {
l.Fatal("could not load NeoFS private key", zap.Error(err))
}
if v.IsSet(cfgTLSKeyFile) && v.IsSet(cfgTLSCertFile) {
tls = &tlsConfig{
KeyFile: v.GetString(cfgTLSKeyFile),
CertFile: v.GetString(cfgTLSCertFile),
}
}
l.Info("using credentials",
zap.String("NeoFS", hex.EncodeToString(key.PublicKey().Bytes())))
opts := &pool.BuilderOptions{
Key: &key.PrivateKey,
NodeConnectionTimeout: conTimeout,
NodeRequestTimeout: reqTimeout,
ClientRebalanceInterval: reBalance,
SessionExpirationEpoch: math.MaxUint64,
2020-07-06 09:18:16 +00:00
}
conns, err = poolPeers.Build(ctx, opts)
if err != nil {
l.Fatal("failed to create connection pool", zap.Error(err))
2020-07-06 09:18:16 +00:00
}
// prepare object layer
obj = layer.NewLayer(l, conns)
// prepare auth center
ctr = auth.New(conns, &key.PrivateKey)
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{
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),
maxClients: api.NewMaxClientsMiddleware(maxClientsCount, maxClientsDeadline),
2020-07-06 09:18:16 +00:00
}
}
func getKeyFromWallet(walletPath, addrStr string, password *string) (*keys.PrivateKey, error) {
if len(walletPath) == 0 {
return nil, fmt.Errorf("wallet path must not be empty")
}
w, err := wallet.NewWalletFromFile(walletPath)
if err != nil {
return nil, err
}
var addr util.Uint160
if len(addrStr) == 0 {
addr = w.GetChangeAddress()
} else {
addr, err = flags.ParseAddress(addrStr)
if err != nil {
return nil, fmt.Errorf("invalid address")
}
}
acc := w.GetAccount(addr)
if acc == nil {
return nil, fmt.Errorf("couldn't find wallet account for %s", addrStr)
}
if password == nil {
pwd, err := input.ReadPassword("Enter password > ")
if err != nil {
return nil, fmt.Errorf("couldn't read password")
}
password = &pwd
}
if err := acc.Decrypt(*password, w.Scrypt); err != nil {
return nil, fmt.Errorf("couldn't decrypt account: %w", err)
}
return acc.PrivateKey(), nil
}
// 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
<-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
}
// 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
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))
}
router := newS3Router()
2020-07-12 23:00:47 +00:00
// Attach app-specific routes:
// attachHealthy(router, a.cli)
attachMetrics(router, a.cfg, a.log)
attachProfiler(router, a.cfg, a.log)
// Attach S3 API:
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)
// Use mux.Router as http.Handler
srv.Handler = router
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))
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
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
}