forked from TrueCloudLab/frostfs-http-gw
[#73] Uploader, downloader structures refactoring
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
parent
add07a21ed
commit
d219943542
27 changed files with 672 additions and 664 deletions
610
cmd/http-gw/app.go
Normal file
610
cmd/http-gw/app.go
Normal file
|
@ -0,0 +1,610 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/frostfs/services"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/metrics"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tree"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||
treepool "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/fasthttp/router"
|
||||
"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/spf13/viper"
|
||||
"github.com/valyala/fasthttp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
app struct {
|
||||
ctx context.Context
|
||||
log *zap.Logger
|
||||
logLevel zap.AtomicLevel
|
||||
pool *pool.Pool
|
||||
treePool *treepool.Pool
|
||||
key *keys.PrivateKey
|
||||
owner *user.ID
|
||||
cfg *viper.Viper
|
||||
webServer *fasthttp.Server
|
||||
webDone chan struct{}
|
||||
resolver *resolver.ContainerResolver
|
||||
metrics *gateMetrics
|
||||
services []*metrics.Service
|
||||
settings *handler.Settings
|
||||
servers []Server
|
||||
}
|
||||
|
||||
// App is an interface for the main gateway function.
|
||||
App interface {
|
||||
Wait()
|
||||
Serve()
|
||||
}
|
||||
|
||||
// Option is an application option.
|
||||
Option func(a *app)
|
||||
|
||||
gateMetrics struct {
|
||||
logger *zap.Logger
|
||||
provider *metrics.GateMetrics
|
||||
mu sync.RWMutex
|
||||
enabled bool
|
||||
}
|
||||
)
|
||||
|
||||
// WithLogger returns Option to set a specific logger.
|
||||
func WithLogger(l *zap.Logger, lvl zap.AtomicLevel) Option {
|
||||
return func(a *app) {
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
a.log = l
|
||||
a.logLevel = lvl
|
||||
}
|
||||
}
|
||||
|
||||
// WithConfig returns Option to use specific Viper configuration.
|
||||
func WithConfig(c *viper.Viper) Option {
|
||||
return func(a *app) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
a.cfg = c
|
||||
}
|
||||
}
|
||||
|
||||
func newApp(ctx context.Context, opt ...Option) App {
|
||||
a := &app{
|
||||
ctx: ctx,
|
||||
log: zap.L(),
|
||||
cfg: viper.GetViper(),
|
||||
webServer: new(fasthttp.Server),
|
||||
webDone: make(chan struct{}),
|
||||
}
|
||||
for i := range opt {
|
||||
opt[i](a)
|
||||
}
|
||||
|
||||
// -- setup FastHTTP server --
|
||||
a.webServer.Name = "frost-http-gw"
|
||||
a.webServer.ReadBufferSize = a.cfg.GetInt(cfgWebReadBufferSize)
|
||||
a.webServer.WriteBufferSize = a.cfg.GetInt(cfgWebWriteBufferSize)
|
||||
a.webServer.ReadTimeout = a.cfg.GetDuration(cfgWebReadTimeout)
|
||||
a.webServer.WriteTimeout = a.cfg.GetDuration(cfgWebWriteTimeout)
|
||||
a.webServer.DisableHeaderNamesNormalizing = true
|
||||
a.webServer.NoDefaultServerHeader = true
|
||||
a.webServer.NoDefaultContentType = true
|
||||
a.webServer.MaxRequestBodySize = a.cfg.GetInt(cfgWebMaxRequestBodySize)
|
||||
a.webServer.DisablePreParseMultipartForm = true
|
||||
a.webServer.StreamRequestBody = a.cfg.GetBool(cfgWebStreamRequestBody)
|
||||
// -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
a.pool, a.treePool, a.key = getPools(ctx, a.log, a.cfg)
|
||||
|
||||
var owner user.ID
|
||||
user.IDFromKey(&owner, a.key.PrivateKey.PublicKey)
|
||||
a.owner = &owner
|
||||
|
||||
a.setRuntimeParameters()
|
||||
|
||||
a.initAppSettings()
|
||||
a.initResolver()
|
||||
a.initMetrics()
|
||||
a.initTracing(ctx)
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *app) initAppSettings() {
|
||||
a.settings = &handler.Settings{}
|
||||
|
||||
a.updateSettings()
|
||||
}
|
||||
|
||||
func (a *app) initResolver() {
|
||||
var err error
|
||||
a.resolver, err = resolver.NewContainerResolver(a.getResolverConfig())
|
||||
if err != nil {
|
||||
a.log.Fatal(logs.FailedToCreateResolver, zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) getResolverConfig() ([]string, *resolver.Config) {
|
||||
resolveCfg := &resolver.Config{
|
||||
FrostFS: resolver.NewFrostFSResolver(a.pool),
|
||||
RPCAddress: a.cfg.GetString(cfgRPCEndpoint),
|
||||
}
|
||||
|
||||
order := a.cfg.GetStringSlice(cfgResolveOrder)
|
||||
if resolveCfg.RPCAddress == "" {
|
||||
order = remove(order, resolver.NNSResolver)
|
||||
a.log.Warn(logs.ResolverNNSWontBeUsedSinceRPCEndpointIsntProvided)
|
||||
}
|
||||
|
||||
if len(order) == 0 {
|
||||
a.log.Info(logs.ContainerResolverWillBeDisabledBecauseOfResolversResolverOrderIsEmpty)
|
||||
}
|
||||
|
||||
return order, resolveCfg
|
||||
}
|
||||
|
||||
func (a *app) initMetrics() {
|
||||
gateMetricsProvider := metrics.NewGateMetrics(a.pool)
|
||||
a.metrics = newGateMetrics(a.log, gateMetricsProvider, a.cfg.GetBool(cfgPrometheusEnabled))
|
||||
a.metrics.SetHealth(metrics.HealthStatusStarting)
|
||||
}
|
||||
|
||||
func newGateMetrics(logger *zap.Logger, provider *metrics.GateMetrics, enabled bool) *gateMetrics {
|
||||
if !enabled {
|
||||
logger.Warn(logs.MetricsAreDisabled)
|
||||
}
|
||||
return &gateMetrics{
|
||||
logger: logger,
|
||||
provider: provider,
|
||||
enabled: enabled,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *gateMetrics) isEnabled() bool {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
return m.enabled
|
||||
}
|
||||
|
||||
func (m *gateMetrics) SetEnabled(enabled bool) {
|
||||
if !enabled {
|
||||
m.logger.Warn(logs.MetricsAreDisabled)
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.enabled = enabled
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *gateMetrics) SetHealth(status metrics.HealthStatus) {
|
||||
if !m.isEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
m.provider.SetHealth(status)
|
||||
}
|
||||
|
||||
func (m *gateMetrics) SetVersion(ver string) {
|
||||
if !m.isEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
m.provider.SetVersion(ver)
|
||||
}
|
||||
|
||||
func (m *gateMetrics) Shutdown() {
|
||||
m.mu.Lock()
|
||||
if m.enabled {
|
||||
m.provider.SetHealth(metrics.HealthStatusShuttingDown)
|
||||
m.enabled = false
|
||||
}
|
||||
m.provider.Unregister()
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *gateMetrics) MarkHealthy(endpoint string) {
|
||||
if !m.isEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
m.provider.MarkHealthy(endpoint)
|
||||
}
|
||||
|
||||
func (m *gateMetrics) MarkUnhealthy(endpoint string) {
|
||||
if !m.isEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
m.provider.MarkUnhealthy(endpoint)
|
||||
}
|
||||
|
||||
func remove(list []string, element string) []string {
|
||||
for i, item := range list {
|
||||
if item == element {
|
||||
return append(list[:i], list[i+1:]...)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func getFrostFSKey(cfg *viper.Viper, log *zap.Logger) (*keys.PrivateKey, error) {
|
||||
walletPath := cfg.GetString(cfgWalletPath)
|
||||
|
||||
if len(walletPath) == 0 {
|
||||
log.Info(logs.NoWalletPathSpecifiedCreatingEphemeralKeyAutomaticallyForThisRun)
|
||||
key, err := keys.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
w, err := wallet.NewWalletFromFile(walletPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var password *string
|
||||
if cfg.IsSet(cfgWalletPassphrase) {
|
||||
pwd := cfg.GetString(cfgWalletPassphrase)
|
||||
password = &pwd
|
||||
}
|
||||
|
||||
address := cfg.GetString(cfgWalletAddress)
|
||||
|
||||
return getKeyFromWallet(w, address, password)
|
||||
}
|
||||
|
||||
func getKeyFromWallet(w *wallet.Wallet, addrStr string, password *string) (*keys.PrivateKey, error) {
|
||||
var addr util.Uint160
|
||||
var err error
|
||||
|
||||
if addrStr == "" {
|
||||
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
|
||||
}
|
||||
|
||||
func (a *app) Wait() {
|
||||
a.log.Info(logs.StartingApplication, zap.String("app_name", "frostfs-http-gw"), zap.String("version", Version))
|
||||
|
||||
a.metrics.SetVersion(Version)
|
||||
a.setHealthStatus()
|
||||
|
||||
<-a.webDone // wait for web-server to be stopped
|
||||
}
|
||||
|
||||
func (a *app) setHealthStatus() {
|
||||
a.metrics.SetHealth(metrics.HealthStatusReady)
|
||||
}
|
||||
|
||||
func (a *app) Serve() {
|
||||
handler := handler.New(a.AppParams(), a.settings, tree.NewTree(services.NewPoolWrapper(a.treePool)))
|
||||
|
||||
// Configure router.
|
||||
a.configureRouter(handler)
|
||||
|
||||
a.startServices()
|
||||
a.initServers(a.ctx)
|
||||
|
||||
for i := range a.servers {
|
||||
go func(i int) {
|
||||
a.log.Info(logs.StartingServer, zap.String("address", a.servers[i].Address()))
|
||||
if err := a.webServer.Serve(a.servers[i].Listener()); err != nil && err != http.ErrServerClosed {
|
||||
a.metrics.MarkUnhealthy(a.servers[i].Address())
|
||||
a.log.Fatal(logs.ListenAndServe, zap.Error(err))
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGHUP)
|
||||
|
||||
LOOP:
|
||||
for {
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
break LOOP
|
||||
case <-sigs:
|
||||
a.configReload(a.ctx)
|
||||
}
|
||||
}
|
||||
|
||||
a.log.Info(logs.ShuttingDownWebServer, zap.Error(a.webServer.Shutdown()))
|
||||
|
||||
a.metrics.Shutdown()
|
||||
a.stopServices()
|
||||
a.shutdownTracing()
|
||||
|
||||
close(a.webDone)
|
||||
}
|
||||
|
||||
func (a *app) shutdownTracing() {
|
||||
const tracingShutdownTimeout = 5 * time.Second
|
||||
shdnCtx, cancel := context.WithTimeout(context.Background(), tracingShutdownTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := tracing.Shutdown(shdnCtx); err != nil {
|
||||
a.log.Warn(logs.FailedToShutdownTracing, zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) configReload(ctx context.Context) {
|
||||
a.log.Info(logs.SIGHUPConfigReloadStarted)
|
||||
if !a.cfg.IsSet(cmdConfig) && !a.cfg.IsSet(cmdConfigDir) {
|
||||
a.log.Warn(logs.FailedToReloadConfigBecauseItsMissed)
|
||||
return
|
||||
}
|
||||
if err := readInConfig(a.cfg); err != nil {
|
||||
a.log.Warn(logs.FailedToReloadConfig, zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if lvl, err := getLogLevel(a.cfg); err != nil {
|
||||
a.log.Warn(logs.LogLevelWontBeUpdated, zap.Error(err))
|
||||
} else {
|
||||
a.logLevel.SetLevel(lvl)
|
||||
}
|
||||
|
||||
if err := a.resolver.UpdateResolvers(a.getResolverConfig()); err != nil {
|
||||
a.log.Warn(logs.FailedToUpdateResolvers, zap.Error(err))
|
||||
}
|
||||
|
||||
if err := a.updateServers(); err != nil {
|
||||
a.log.Warn(logs.FailedToReloadServerParameters, zap.Error(err))
|
||||
}
|
||||
|
||||
a.setRuntimeParameters()
|
||||
|
||||
a.stopServices()
|
||||
a.startServices()
|
||||
|
||||
a.updateSettings()
|
||||
|
||||
a.metrics.SetEnabled(a.cfg.GetBool(cfgPrometheusEnabled))
|
||||
a.initTracing(ctx)
|
||||
a.setHealthStatus()
|
||||
|
||||
a.log.Info(logs.SIGHUPConfigReloadCompleted)
|
||||
}
|
||||
|
||||
func (a *app) updateSettings() {
|
||||
a.settings.SetDefaultTimestamp(a.cfg.GetBool(cfgUploaderHeaderEnableDefaultTimestamp))
|
||||
a.settings.SetZipCompression(a.cfg.GetBool(cfgZipCompression))
|
||||
}
|
||||
|
||||
func (a *app) startServices() {
|
||||
pprofConfig := metrics.Config{Enabled: a.cfg.GetBool(cfgPprofEnabled), Address: a.cfg.GetString(cfgPprofAddress)}
|
||||
pprofService := metrics.NewPprofService(a.log, pprofConfig)
|
||||
a.services = append(a.services, pprofService)
|
||||
go pprofService.Start()
|
||||
|
||||
prometheusConfig := metrics.Config{Enabled: a.cfg.GetBool(cfgPrometheusEnabled), Address: a.cfg.GetString(cfgPrometheusAddress)}
|
||||
prometheusService := metrics.NewPrometheusService(a.log, prometheusConfig)
|
||||
a.services = append(a.services, prometheusService)
|
||||
go prometheusService.Start()
|
||||
}
|
||||
|
||||
func (a *app) stopServices() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultShutdownTimeout)
|
||||
defer cancel()
|
||||
|
||||
for _, svc := range a.services {
|
||||
svc.ShutDown(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) configureRouter(handler *handler.Handler) {
|
||||
r := router.New()
|
||||
r.RedirectTrailingSlash = true
|
||||
r.NotFound = func(r *fasthttp.RequestCtx) {
|
||||
response.Error(r, "Not found", fasthttp.StatusNotFound)
|
||||
}
|
||||
r.MethodNotAllowed = func(r *fasthttp.RequestCtx) {
|
||||
response.Error(r, "Method Not Allowed", fasthttp.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
r.POST("/upload/{cid}", a.logger(a.tokenizer(a.tracer(handler.Upload))))
|
||||
a.log.Info(logs.AddedPathUploadCid)
|
||||
r.GET("/get/{cid}/{oid:*}", a.logger(a.tokenizer(a.tracer(handler.DownloadByAddressOrBucketName))))
|
||||
r.HEAD("/get/{cid}/{oid:*}", a.logger(a.tokenizer(a.tracer(handler.HeadByAddressOrBucketName))))
|
||||
a.log.Info(logs.AddedPathGetCidOid)
|
||||
r.GET("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.logger(a.tokenizer(a.tracer(handler.DownloadByAttribute))))
|
||||
r.HEAD("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.logger(a.tokenizer(a.tracer(handler.HeadByAttribute))))
|
||||
a.log.Info(logs.AddedPathGetByAttributeCidAttrKeyAttrVal)
|
||||
r.GET("/zip/{cid}/{prefix:*}", a.logger(a.tokenizer(a.tracer(handler.DownloadZipped))))
|
||||
a.log.Info(logs.AddedPathZipCidPrefix)
|
||||
|
||||
a.webServer.Handler = r.Handler
|
||||
}
|
||||
|
||||
func (a *app) logger(h fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||
return func(req *fasthttp.RequestCtx) {
|
||||
a.log.Info(logs.Request, zap.String("remote", req.RemoteAddr().String()),
|
||||
zap.ByteString("method", req.Method()),
|
||||
zap.ByteString("path", req.Path()),
|
||||
zap.ByteString("query", req.QueryArgs().QueryString()),
|
||||
zap.Uint64("id", req.ID()))
|
||||
h(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) tokenizer(h fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||
return func(req *fasthttp.RequestCtx) {
|
||||
appCtx, err := tokens.StoreBearerTokenAppCtx(a.ctx, req)
|
||||
if err != nil {
|
||||
a.log.Error(logs.CouldNotFetchAndStoreBearerToken, zap.Error(err))
|
||||
response.Error(req, "could not fetch and store bearer token: "+err.Error(), fasthttp.StatusBadRequest)
|
||||
}
|
||||
utils.SetContextToRequest(appCtx, req)
|
||||
h(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) tracer(h fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||
return func(req *fasthttp.RequestCtx) {
|
||||
appCtx := utils.GetContextFromRequest(req)
|
||||
|
||||
appCtx, span := utils.StartHTTPServerSpan(appCtx, req, "REQUEST")
|
||||
defer func() {
|
||||
utils.SetHTTPTraceInfo(appCtx, span, req)
|
||||
span.End()
|
||||
}()
|
||||
|
||||
utils.SetContextToRequest(appCtx, req)
|
||||
h(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) AppParams() *utils.AppParams {
|
||||
return &utils.AppParams{
|
||||
Logger: a.log,
|
||||
Pool: a.pool,
|
||||
Owner: a.owner,
|
||||
Resolver: a.resolver,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) initServers(ctx context.Context) {
|
||||
serversInfo := fetchServers(a.cfg)
|
||||
|
||||
a.servers = make([]Server, 0, len(serversInfo))
|
||||
for _, serverInfo := range serversInfo {
|
||||
fields := []zap.Field{
|
||||
zap.String("address", serverInfo.Address), zap.Bool("tls enabled", serverInfo.TLS.Enabled),
|
||||
zap.String("tls cert", serverInfo.TLS.CertFile), zap.String("tls key", serverInfo.TLS.KeyFile),
|
||||
}
|
||||
srv, err := newServer(ctx, serverInfo)
|
||||
if err != nil {
|
||||
a.metrics.MarkUnhealthy(serverInfo.Address)
|
||||
a.log.Warn(logs.FailedToAddServer, append(fields, zap.Error(err))...)
|
||||
continue
|
||||
}
|
||||
a.metrics.MarkHealthy(serverInfo.Address)
|
||||
|
||||
a.servers = append(a.servers, srv)
|
||||
a.log.Info(logs.AddServer, fields...)
|
||||
}
|
||||
|
||||
if len(a.servers) == 0 {
|
||||
a.log.Fatal(logs.NoHealthyServers)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) updateServers() error {
|
||||
serversInfo := fetchServers(a.cfg)
|
||||
|
||||
var found bool
|
||||
for _, serverInfo := range serversInfo {
|
||||
index := a.serverIndex(serverInfo.Address)
|
||||
if index == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if serverInfo.TLS.Enabled {
|
||||
if err := a.servers[index].UpdateCert(serverInfo.TLS.CertFile, serverInfo.TLS.KeyFile); err != nil {
|
||||
return fmt.Errorf("failed to update tls certs: %w", err)
|
||||
}
|
||||
}
|
||||
found = true
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("invalid servers configuration: no known server found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *app) serverIndex(address string) int {
|
||||
for i := range a.servers {
|
||||
if a.servers[i].Address() == address {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (a *app) initTracing(ctx context.Context) {
|
||||
instanceID := ""
|
||||
if len(a.servers) > 0 {
|
||||
instanceID = a.servers[0].Address()
|
||||
}
|
||||
cfg := tracing.Config{
|
||||
Enabled: a.cfg.GetBool(cfgTracingEnabled),
|
||||
Exporter: tracing.Exporter(a.cfg.GetString(cfgTracingExporter)),
|
||||
Endpoint: a.cfg.GetString(cfgTracingEndpoint),
|
||||
Service: "frostfs-http-gw",
|
||||
InstanceID: instanceID,
|
||||
Version: Version,
|
||||
}
|
||||
updated, err := tracing.Setup(ctx, cfg)
|
||||
if err != nil {
|
||||
a.log.Warn(logs.FailedToInitializeTracing, zap.Error(err))
|
||||
}
|
||||
if updated {
|
||||
a.log.Info(logs.TracingConfigUpdated)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *app) setRuntimeParameters() {
|
||||
if len(os.Getenv("GOMEMLIMIT")) != 0 {
|
||||
// default limit < yaml limit < app env limit < GOMEMLIMIT
|
||||
a.log.Warn(logs.RuntimeSoftMemoryDefinedWithGOMEMLIMIT)
|
||||
return
|
||||
}
|
||||
|
||||
softMemoryLimit := fetchSoftMemoryLimit(a.cfg)
|
||||
previous := debug.SetMemoryLimit(softMemoryLimit)
|
||||
if softMemoryLimit != previous {
|
||||
a.log.Info(logs.RuntimeSoftMemoryLimitUpdated,
|
||||
zap.Int64("new_value", softMemoryLimit),
|
||||
zap.Int64("old_value", previous))
|
||||
}
|
||||
}
|
442
cmd/http-gw/integration_test.go
Normal file
442
cmd/http-gw/integration_test.go
Normal file
|
@ -0,0 +1,442 @@
|
|||
//go:build integration
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
containerv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
)
|
||||
|
||||
type putResponse struct {
|
||||
CID string `json:"container_id"`
|
||||
OID string `json:"object_id"`
|
||||
}
|
||||
|
||||
const (
|
||||
testContainerName = "friendly"
|
||||
testListenAddress = "localhost:8082"
|
||||
testHost = "http://" + testListenAddress
|
||||
)
|
||||
|
||||
func TestIntegration(t *testing.T) {
|
||||
rootCtx := context.Background()
|
||||
aioImage := "truecloudlab/frostfs-aio:"
|
||||
versions := []string{
|
||||
"1.2.7", // frostfs-storage v0.36.0 RC
|
||||
}
|
||||
key, err := keys.NewPrivateKeyFromHex("1dd37fba80fec4e6a6f13fd708d8dcb3b29def768017052f6c930fa1c5d90bbb")
|
||||
require.NoError(t, err)
|
||||
|
||||
var ownerID user.ID
|
||||
user.IDFromKey(&ownerID, key.PrivateKey.PublicKey)
|
||||
|
||||
for _, version := range versions {
|
||||
ctx, cancel2 := context.WithCancel(rootCtx)
|
||||
|
||||
aioContainer := createDockerContainer(ctx, t, aioImage+version)
|
||||
server, cancel := runServer()
|
||||
clientPool := getPool(ctx, t, key)
|
||||
CID, err := createContainer(ctx, t, clientPool, ownerID, version)
|
||||
require.NoError(t, err, version)
|
||||
|
||||
t.Run("simple put "+version, func(t *testing.T) { simplePut(ctx, t, clientPool, CID, version) })
|
||||
t.Run("put with duplicate keys "+version, func(t *testing.T) { putWithDuplicateKeys(t, CID) })
|
||||
t.Run("simple get "+version, func(t *testing.T) { simpleGet(ctx, t, clientPool, ownerID, CID, version) })
|
||||
t.Run("get by attribute "+version, func(t *testing.T) { getByAttr(ctx, t, clientPool, ownerID, CID, version) })
|
||||
t.Run("get zip "+version, func(t *testing.T) { getZip(ctx, t, clientPool, ownerID, CID, version) })
|
||||
|
||||
cancel()
|
||||
server.Wait()
|
||||
err = aioContainer.Terminate(ctx)
|
||||
require.NoError(t, err)
|
||||
cancel2()
|
||||
}
|
||||
}
|
||||
|
||||
func runServer() (App, context.CancelFunc) {
|
||||
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
v := getDefaultConfig()
|
||||
l, lvl := newLogger(v)
|
||||
application := newApp(cancelCtx, WithConfig(v), WithLogger(l, lvl))
|
||||
go application.Serve()
|
||||
|
||||
return application, cancel
|
||||
}
|
||||
|
||||
func simplePut(ctx context.Context, t *testing.T, p *pool.Pool, CID cid.ID, version string) {
|
||||
url := testHost + "/upload/" + CID.String()
|
||||
makePutRequestAndCheck(ctx, t, p, CID, url)
|
||||
|
||||
url = testHost + "/upload/" + testContainerName
|
||||
makePutRequestAndCheck(ctx, t, p, CID, url)
|
||||
}
|
||||
|
||||
func makePutRequestAndCheck(ctx context.Context, t *testing.T, p *pool.Pool, cnrID cid.ID, url string) {
|
||||
content := "content of file"
|
||||
keyAttr, valAttr := "User-Attribute", "user value"
|
||||
attributes := map[string]string{
|
||||
object.AttributeFileName: "newFile.txt",
|
||||
keyAttr: valAttr,
|
||||
}
|
||||
|
||||
var buff bytes.Buffer
|
||||
w := multipart.NewWriter(&buff)
|
||||
fw, err := w.CreateFormFile("file", attributes[object.AttributeFileName])
|
||||
require.NoError(t, err)
|
||||
_, err = io.Copy(fw, bytes.NewBufferString(content))
|
||||
require.NoError(t, err)
|
||||
err = w.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
request, err := http.NewRequest(http.MethodPost, url, &buff)
|
||||
require.NoError(t, err)
|
||||
request.Header.Set("Content-Type", w.FormDataContentType())
|
||||
request.Header.Set("X-Attribute-"+keyAttr, valAttr)
|
||||
|
||||
resp, err := http.DefaultClient.Do(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
addr := &putResponse{}
|
||||
err = json.Unmarshal(body, addr)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cnrID.DecodeString(addr.CID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var id oid.ID
|
||||
err = id.DecodeString(addr.OID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var objectAddress oid.Address
|
||||
objectAddress.SetContainer(cnrID)
|
||||
objectAddress.SetObject(id)
|
||||
|
||||
payload := bytes.NewBuffer(nil)
|
||||
|
||||
var prm pool.PrmObjectGet
|
||||
prm.SetAddress(objectAddress)
|
||||
|
||||
res, err := p.GetObject(ctx, prm)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = io.Copy(payload, res.Payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, content, payload.String())
|
||||
|
||||
for _, attribute := range res.Header.Attributes() {
|
||||
require.Equal(t, attributes[attribute.Key()], attribute.Value())
|
||||
}
|
||||
}
|
||||
|
||||
func putWithDuplicateKeys(t *testing.T, CID cid.ID) {
|
||||
url := testHost + "/upload/" + CID.String()
|
||||
|
||||
attr := "X-Attribute-User-Attribute"
|
||||
content := "content of file"
|
||||
valOne, valTwo := "first_value", "second_value"
|
||||
fileName := "newFile.txt"
|
||||
|
||||
var buff bytes.Buffer
|
||||
w := multipart.NewWriter(&buff)
|
||||
fw, err := w.CreateFormFile("file", fileName)
|
||||
require.NoError(t, err)
|
||||
_, err = io.Copy(fw, bytes.NewBufferString(content))
|
||||
require.NoError(t, err)
|
||||
err = w.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
request, err := http.NewRequest(http.MethodPost, url, &buff)
|
||||
require.NoError(t, err)
|
||||
request.Header.Set("Content-Type", w.FormDataContentType())
|
||||
request.Header.Add(attr, valOne)
|
||||
request.Header.Add(attr, valTwo)
|
||||
|
||||
resp, err := http.DefaultClient.Do(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "key duplication error: "+attr+"\n", string(body))
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
}
|
||||
|
||||
func simpleGet(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, CID cid.ID, version string) {
|
||||
content := "content of file"
|
||||
attributes := map[string]string{
|
||||
"some-attr": "some-get-value",
|
||||
}
|
||||
|
||||
id := putObject(ctx, t, clientPool, ownerID, CID, content, attributes)
|
||||
|
||||
resp, err := http.Get(testHost + "/get/" + CID.String() + "/" + id.String())
|
||||
require.NoError(t, err)
|
||||
checkGetResponse(t, resp, content, attributes)
|
||||
|
||||
resp, err = http.Get(testHost + "/get/" + testContainerName + "/" + id.String())
|
||||
require.NoError(t, err)
|
||||
checkGetResponse(t, resp, content, attributes)
|
||||
}
|
||||
|
||||
func checkGetResponse(t *testing.T, resp *http.Response, content string, attributes map[string]string) {
|
||||
defer func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, content, string(data))
|
||||
|
||||
for k, v := range attributes {
|
||||
require.Equal(t, v, resp.Header.Get("X-Attribute-"+k))
|
||||
}
|
||||
}
|
||||
|
||||
func checkGetByAttrResponse(t *testing.T, resp *http.Response, content string, attributes map[string]string) {
|
||||
defer func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, content, string(data))
|
||||
|
||||
for k, v := range attributes {
|
||||
require.Equal(t, v, resp.Header.Get(k))
|
||||
}
|
||||
}
|
||||
|
||||
func getByAttr(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, CID cid.ID, version string) {
|
||||
keyAttr, valAttr := "some-attr", "some-get-by-attr-value"
|
||||
content := "content of file"
|
||||
attributes := map[string]string{keyAttr: valAttr}
|
||||
|
||||
id := putObject(ctx, t, clientPool, ownerID, CID, content, attributes)
|
||||
|
||||
expectedAttr := map[string]string{
|
||||
"X-Attribute-" + keyAttr: valAttr,
|
||||
"x-object-id": id.String(),
|
||||
"x-container-id": CID.String(),
|
||||
}
|
||||
|
||||
resp, err := http.Get(testHost + "/get_by_attribute/" + CID.String() + "/" + keyAttr + "/" + valAttr)
|
||||
require.NoError(t, err)
|
||||
checkGetByAttrResponse(t, resp, content, expectedAttr)
|
||||
|
||||
resp, err = http.Get(testHost + "/get_by_attribute/" + testContainerName + "/" + keyAttr + "/" + valAttr)
|
||||
require.NoError(t, err)
|
||||
checkGetByAttrResponse(t, resp, content, expectedAttr)
|
||||
}
|
||||
|
||||
func getZip(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, CID cid.ID, version string) {
|
||||
names := []string{"zipfolder/dir/name1.txt", "zipfolder/name2.txt"}
|
||||
contents := []string{"content of file1", "content of file2"}
|
||||
attributes1 := map[string]string{object.AttributeFilePath: names[0]}
|
||||
attributes2 := map[string]string{object.AttributeFilePath: names[1]}
|
||||
|
||||
putObject(ctx, t, clientPool, ownerID, CID, contents[0], attributes1)
|
||||
putObject(ctx, t, clientPool, ownerID, CID, contents[1], attributes2)
|
||||
|
||||
baseURL := testHost + "/zip/" + CID.String()
|
||||
makeZipTest(t, baseURL, names, contents)
|
||||
|
||||
baseURL = testHost + "/zip/" + testContainerName
|
||||
makeZipTest(t, baseURL, names, contents)
|
||||
}
|
||||
|
||||
func makeZipTest(t *testing.T, baseURL string, names, contents []string) {
|
||||
url := baseURL + "/zipfolder"
|
||||
makeZipRequest(t, url, names, contents)
|
||||
|
||||
// check nested folder
|
||||
url = baseURL + "/zipfolder/dir"
|
||||
makeZipRequest(t, url, names[:1], contents[:1])
|
||||
}
|
||||
|
||||
func makeZipRequest(t *testing.T, url string, names, contents []string) {
|
||||
resp, err := http.Get(url)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
checkZip(t, data, int64(len(data)), names, contents)
|
||||
}
|
||||
|
||||
func checkZip(t *testing.T, data []byte, length int64, names, contents []string) {
|
||||
readerAt := bytes.NewReader(data)
|
||||
|
||||
zipReader, err := zip.NewReader(readerAt, length)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, len(names), len(zipReader.File))
|
||||
|
||||
sort.Slice(zipReader.File, func(i, j int) bool {
|
||||
return zipReader.File[i].FileHeader.Name < zipReader.File[j].FileHeader.Name
|
||||
})
|
||||
|
||||
for i, f := range zipReader.File {
|
||||
require.Equal(t, names[i], f.FileHeader.Name)
|
||||
|
||||
rc, err := f.Open()
|
||||
require.NoError(t, err)
|
||||
|
||||
all, err := io.ReadAll(rc)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, contents[i], string(all))
|
||||
|
||||
err = rc.Close()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func createDockerContainer(ctx context.Context, t *testing.T, image string) testcontainers.Container {
|
||||
req := testcontainers.ContainerRequest{
|
||||
Image: image,
|
||||
WaitingFor: wait.NewLogStrategy("aio container started").WithStartupTimeout(30 * time.Second),
|
||||
Name: "aio",
|
||||
Hostname: "aio",
|
||||
NetworkMode: "host",
|
||||
}
|
||||
aioC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: req,
|
||||
Started: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return aioC
|
||||
}
|
||||
|
||||
func getDefaultConfig() *viper.Viper {
|
||||
v := settings()
|
||||
v.SetDefault(cfgPeers+".0.address", "localhost:8080")
|
||||
v.SetDefault(cfgPeers+".0.weight", 1)
|
||||
v.SetDefault(cfgPeers+".0.priority", 1)
|
||||
|
||||
v.SetDefault(cfgRPCEndpoint, "http://localhost:30333")
|
||||
v.SetDefault("server.0.address", testListenAddress)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func getPool(ctx context.Context, t *testing.T, key *keys.PrivateKey) *pool.Pool {
|
||||
var prm pool.InitParameters
|
||||
prm.SetKey(&key.PrivateKey)
|
||||
prm.SetNodeDialTimeout(5 * time.Second)
|
||||
prm.AddNode(pool.NewNodeParam(1, "localhost:8080", 1))
|
||||
|
||||
clientPool, err := pool.NewPool(prm)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = clientPool.Dial(ctx)
|
||||
require.NoError(t, err)
|
||||
return clientPool
|
||||
}
|
||||
|
||||
func createContainer(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, version string) (cid.ID, error) {
|
||||
var policy netmap.PlacementPolicy
|
||||
err := policy.DecodeString("REP 1")
|
||||
require.NoError(t, err)
|
||||
|
||||
var cnr container.Container
|
||||
cnr.Init()
|
||||
cnr.SetPlacementPolicy(policy)
|
||||
cnr.SetBasicACL(acl.PublicRWExtended)
|
||||
cnr.SetOwner(ownerID)
|
||||
|
||||
container.SetCreationTime(&cnr, time.Now())
|
||||
|
||||
var domain container.Domain
|
||||
domain.SetName(testContainerName)
|
||||
|
||||
cnr.SetAttribute(containerv2.SysAttributeName, domain.Name())
|
||||
cnr.SetAttribute(containerv2.SysAttributeZone, domain.Zone())
|
||||
|
||||
var waitPrm pool.WaitParams
|
||||
waitPrm.SetTimeout(15 * time.Second)
|
||||
waitPrm.SetPollInterval(3 * time.Second)
|
||||
|
||||
var prm pool.PrmContainerPut
|
||||
prm.SetContainer(cnr)
|
||||
prm.SetWaitParams(waitPrm)
|
||||
|
||||
CID, err := clientPool.PutContainer(ctx, prm)
|
||||
if err != nil {
|
||||
return cid.ID{}, err
|
||||
}
|
||||
fmt.Println(CID.String())
|
||||
|
||||
return CID, err
|
||||
}
|
||||
|
||||
func putObject(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, CID cid.ID, content string, attributes map[string]string) oid.ID {
|
||||
obj := object.New()
|
||||
obj.SetContainerID(CID)
|
||||
obj.SetOwnerID(&ownerID)
|
||||
|
||||
var attrs []object.Attribute
|
||||
for key, val := range attributes {
|
||||
attr := object.NewAttribute()
|
||||
attr.SetKey(key)
|
||||
attr.SetValue(val)
|
||||
attrs = append(attrs, *attr)
|
||||
}
|
||||
obj.SetAttributes(attrs...)
|
||||
|
||||
var prm pool.PrmObjectPut
|
||||
prm.SetHeader(*obj)
|
||||
prm.SetPayload(bytes.NewBufferString(content))
|
||||
|
||||
id, err := clientPool.PutObject(ctx, prm)
|
||||
require.NoError(t, err)
|
||||
|
||||
return id
|
||||
}
|
17
cmd/http-gw/main.go
Normal file
17
cmd/http-gw/main.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
globalContext, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
v := settings()
|
||||
logger, atomicLevel := newLogger(v)
|
||||
|
||||
application := newApp(globalContext, WithLogger(logger, atomicLevel), WithConfig(v))
|
||||
go application.Serve()
|
||||
application.Wait()
|
||||
}
|
10
cmd/http-gw/misc.go
Normal file
10
cmd/http-gw/misc.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
// Prefix is a prefix used for environment variables containing gateway
|
||||
// configuration.
|
||||
const Prefix = "HTTP_GW"
|
||||
|
||||
var (
|
||||
// Version is the gateway version.
|
||||
Version = "dev"
|
||||
)
|
122
cmd/http-gw/server.go
Normal file
122
cmd/http-gw/server.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type (
|
||||
ServerInfo struct {
|
||||
Address string
|
||||
TLS ServerTLSInfo
|
||||
}
|
||||
|
||||
ServerTLSInfo struct {
|
||||
Enabled bool
|
||||
CertFile string
|
||||
KeyFile string
|
||||
}
|
||||
|
||||
Server interface {
|
||||
Address() string
|
||||
Listener() net.Listener
|
||||
UpdateCert(certFile, keyFile string) error
|
||||
}
|
||||
|
||||
server struct {
|
||||
address string
|
||||
listener net.Listener
|
||||
tlsProvider *certProvider
|
||||
}
|
||||
|
||||
certProvider struct {
|
||||
Enabled bool
|
||||
|
||||
mu sync.RWMutex
|
||||
certPath string
|
||||
keyPath string
|
||||
cert *tls.Certificate
|
||||
}
|
||||
)
|
||||
|
||||
func (s *server) Address() string {
|
||||
return s.address
|
||||
}
|
||||
|
||||
func (s *server) Listener() net.Listener {
|
||||
return s.listener
|
||||
}
|
||||
|
||||
func (s *server) UpdateCert(certFile, keyFile string) error {
|
||||
return s.tlsProvider.UpdateCert(certFile, keyFile)
|
||||
}
|
||||
|
||||
func newServer(ctx context.Context, serverInfo ServerInfo) (*server, error) {
|
||||
var lic net.ListenConfig
|
||||
ln, err := lic.Listen(ctx, "tcp", serverInfo.Address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not prepare listener: %w", err)
|
||||
}
|
||||
|
||||
tlsProvider := &certProvider{
|
||||
Enabled: serverInfo.TLS.Enabled,
|
||||
}
|
||||
|
||||
if serverInfo.TLS.Enabled {
|
||||
if err = tlsProvider.UpdateCert(serverInfo.TLS.CertFile, serverInfo.TLS.KeyFile); err != nil {
|
||||
return nil, fmt.Errorf("failed to update cert: %w", err)
|
||||
}
|
||||
|
||||
ln = tls.NewListener(ln, &tls.Config{
|
||||
GetCertificate: tlsProvider.GetCertificate,
|
||||
})
|
||||
}
|
||||
|
||||
return &server{
|
||||
address: serverInfo.Address,
|
||||
listener: ln,
|
||||
tlsProvider: tlsProvider,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *certProvider) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if !p.Enabled {
|
||||
return nil, errors.New("cert provider: disabled")
|
||||
}
|
||||
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
return p.cert, nil
|
||||
}
|
||||
|
||||
func (p *certProvider) UpdateCert(certPath, keyPath string) error {
|
||||
if !p.Enabled {
|
||||
return fmt.Errorf("tls disabled")
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load TLS key pair from certFile '%s' and keyFile '%s': %w", certPath, keyPath, err)
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
p.certPath = certPath
|
||||
p.keyPath = keyPath
|
||||
p.cert = &cert
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *certProvider) FilePaths() (string, string) {
|
||||
if !p.Enabled {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
return p.certPath, p.keyPath
|
||||
}
|
533
cmd/http-gw/settings.go
Normal file
533
cmd/http-gw/settings.go
Normal file
|
@ -0,0 +1,533 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
|
||||
grpctracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||
treepool "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/valyala/fasthttp"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRebalanceTimer = 60 * time.Second
|
||||
defaultRequestTimeout = 15 * time.Second
|
||||
defaultConnectTimeout = 10 * time.Second
|
||||
defaultStreamTimeout = 10 * time.Second
|
||||
|
||||
defaultShutdownTimeout = 15 * time.Second
|
||||
|
||||
defaultPoolErrorThreshold uint32 = 100
|
||||
|
||||
defaultSoftMemoryLimit = math.MaxInt64
|
||||
|
||||
cfgServer = "server"
|
||||
cfgTLSEnabled = "tls.enabled"
|
||||
cfgTLSCertFile = "tls.cert_file"
|
||||
cfgTLSKeyFile = "tls.key_file"
|
||||
|
||||
// Web.
|
||||
cfgWebReadBufferSize = "web.read_buffer_size"
|
||||
cfgWebWriteBufferSize = "web.write_buffer_size"
|
||||
cfgWebReadTimeout = "web.read_timeout"
|
||||
cfgWebWriteTimeout = "web.write_timeout"
|
||||
cfgWebStreamRequestBody = "web.stream_request_body"
|
||||
cfgWebMaxRequestBodySize = "web.max_request_body_size"
|
||||
|
||||
// Metrics / Profiler.
|
||||
cfgPrometheusEnabled = "prometheus.enabled"
|
||||
cfgPrometheusAddress = "prometheus.address"
|
||||
cfgPprofEnabled = "pprof.enabled"
|
||||
cfgPprofAddress = "pprof.address"
|
||||
|
||||
// Tracing ...
|
||||
cfgTracingEnabled = "tracing.enabled"
|
||||
cfgTracingExporter = "tracing.exporter"
|
||||
cfgTracingEndpoint = "tracing.endpoint"
|
||||
|
||||
// Pool config.
|
||||
cfgConTimeout = "connect_timeout"
|
||||
cfgStreamTimeout = "stream_timeout"
|
||||
cfgReqTimeout = "request_timeout"
|
||||
cfgRebalance = "rebalance_timer"
|
||||
cfgPoolErrorThreshold = "pool_error_threshold"
|
||||
|
||||
// Logger.
|
||||
cfgLoggerLevel = "logger.level"
|
||||
|
||||
// Wallet.
|
||||
cfgWalletPassphrase = "wallet.passphrase"
|
||||
cfgWalletPath = "wallet.path"
|
||||
cfgWalletAddress = "wallet.address"
|
||||
|
||||
// Uploader Header.
|
||||
cfgUploaderHeaderEnableDefaultTimestamp = "upload_header.use_default_timestamp"
|
||||
|
||||
// Peers.
|
||||
cfgPeers = "peers"
|
||||
|
||||
// NeoGo.
|
||||
cfgRPCEndpoint = "rpc_endpoint"
|
||||
|
||||
// Resolving.
|
||||
cfgResolveOrder = "resolve_order"
|
||||
|
||||
// Zip compression.
|
||||
cfgZipCompression = "zip.compression"
|
||||
|
||||
// Runtime.
|
||||
cfgSoftMemoryLimit = "runtime.soft_memory_limit"
|
||||
|
||||
// Command line args.
|
||||
cmdHelp = "help"
|
||||
cmdVersion = "version"
|
||||
cmdPprof = "pprof"
|
||||
cmdMetrics = "metrics"
|
||||
cmdWallet = "wallet"
|
||||
cmdAddress = "address"
|
||||
cmdConfig = "config"
|
||||
cmdConfigDir = "config-dir"
|
||||
cmdListenAddress = "listen_address"
|
||||
)
|
||||
|
||||
var ignore = map[string]struct{}{
|
||||
cfgPeers: {},
|
||||
cmdHelp: {},
|
||||
cmdVersion: {},
|
||||
}
|
||||
|
||||
func settings() *viper.Viper {
|
||||
v := viper.New()
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvPrefix(Prefix)
|
||||
v.AllowEmptyEnv(true)
|
||||
v.SetConfigType("yaml")
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
|
||||
// flags setup:
|
||||
flags := pflag.NewFlagSet("commandline", pflag.ExitOnError)
|
||||
flags.SetOutput(os.Stdout)
|
||||
flags.SortFlags = false
|
||||
|
||||
flags.Bool(cmdPprof, false, "enable pprof")
|
||||
flags.Bool(cmdMetrics, false, "enable prometheus")
|
||||
|
||||
help := flags.BoolP(cmdHelp, "h", false, "show help")
|
||||
version := flags.BoolP(cmdVersion, "v", false, "show version")
|
||||
|
||||
flags.StringP(cmdWallet, "w", "", `path to the wallet`)
|
||||
flags.String(cmdAddress, "", `address of wallet account`)
|
||||
flags.StringArray(cmdConfig, nil, "config paths")
|
||||
flags.String(cmdConfigDir, "", "config dir path")
|
||||
flags.Duration(cfgConTimeout, defaultConnectTimeout, "gRPC connect timeout")
|
||||
flags.Duration(cfgStreamTimeout, defaultStreamTimeout, "gRPC individual message timeout")
|
||||
flags.Duration(cfgReqTimeout, defaultRequestTimeout, "gRPC request timeout")
|
||||
flags.Duration(cfgRebalance, defaultRebalanceTimer, "gRPC connection rebalance timer")
|
||||
|
||||
flags.String(cmdListenAddress, "0.0.0.0:8080", "addresses to listen")
|
||||
flags.String(cfgTLSCertFile, "", "TLS certificate path")
|
||||
flags.String(cfgTLSKeyFile, "", "TLS key path")
|
||||
peers := flags.StringArrayP(cfgPeers, "p", nil, "FrostFS nodes")
|
||||
|
||||
resolveMethods := flags.StringSlice(cfgResolveOrder, []string{resolver.NNSResolver, resolver.DNSResolver}, "set container name resolve order")
|
||||
|
||||
// set defaults:
|
||||
|
||||
// logger:
|
||||
v.SetDefault(cfgLoggerLevel, "debug")
|
||||
|
||||
// pool:
|
||||
v.SetDefault(cfgPoolErrorThreshold, defaultPoolErrorThreshold)
|
||||
|
||||
// web-server:
|
||||
v.SetDefault(cfgWebReadBufferSize, 4096)
|
||||
v.SetDefault(cfgWebWriteBufferSize, 4096)
|
||||
v.SetDefault(cfgWebReadTimeout, time.Minute*10)
|
||||
v.SetDefault(cfgWebWriteTimeout, time.Minute*5)
|
||||
v.SetDefault(cfgWebStreamRequestBody, true)
|
||||
v.SetDefault(cfgWebMaxRequestBodySize, fasthttp.DefaultMaxRequestBodySize)
|
||||
|
||||
// upload header
|
||||
v.SetDefault(cfgUploaderHeaderEnableDefaultTimestamp, false)
|
||||
|
||||
// zip:
|
||||
v.SetDefault(cfgZipCompression, false)
|
||||
|
||||
// metrics
|
||||
v.SetDefault(cfgPprofAddress, "localhost:8083")
|
||||
v.SetDefault(cfgPrometheusAddress, "localhost:8084")
|
||||
|
||||
// Binding flags
|
||||
if err := v.BindPFlag(cfgPprofEnabled, flags.Lookup(cmdPprof)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := v.BindPFlag(cfgPrometheusEnabled, flags.Lookup(cmdMetrics)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := v.BindPFlag(cfgWalletPath, flags.Lookup(cmdWallet)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := v.BindPFlag(cfgWalletAddress, flags.Lookup(cmdAddress)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := v.BindPFlags(flags); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := v.BindPFlag(cfgServer+".0.address", flags.Lookup(cmdListenAddress)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := v.BindPFlag(cfgServer+".0."+cfgTLSKeyFile, flags.Lookup(cfgTLSKeyFile)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := v.BindPFlag(cfgServer+".0."+cfgTLSCertFile, flags.Lookup(cfgTLSCertFile)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := flags.Parse(os.Args); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if v.IsSet(cfgServer+".0."+cfgTLSKeyFile) && v.IsSet(cfgServer+".0."+cfgTLSCertFile) {
|
||||
v.Set(cfgServer+".0."+cfgTLSEnabled, true)
|
||||
}
|
||||
|
||||
if resolveMethods != nil {
|
||||
v.SetDefault(cfgResolveOrder, *resolveMethods)
|
||||
}
|
||||
|
||||
switch {
|
||||
case help != nil && *help:
|
||||
fmt.Printf("FrostFS HTTP Gateway %s\n", Version)
|
||||
flags.PrintDefaults()
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Default environments:")
|
||||
fmt.Println()
|
||||
keys := v.AllKeys()
|
||||
sort.Strings(keys)
|
||||
|
||||
for i := range keys {
|
||||
if _, ok := ignore[keys[i]]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
defaultValue := v.GetString(keys[i])
|
||||
if len(defaultValue) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
k := strings.Replace(keys[i], ".", "_", -1)
|
||||
fmt.Printf("%s_%s = %s\n", Prefix, strings.ToUpper(k), defaultValue)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Peers preset:")
|
||||
fmt.Println()
|
||||
|
||||
fmt.Printf("%s_%s_[N]_ADDRESS = string\n", Prefix, strings.ToUpper(cfgPeers))
|
||||
fmt.Printf("%s_%s_[N]_WEIGHT = float\n", Prefix, strings.ToUpper(cfgPeers))
|
||||
|
||||
os.Exit(0)
|
||||
case version != nil && *version:
|
||||
fmt.Printf("FrostFS HTTP Gateway\nVersion: %s\nGoVersion: %s\n", Version, runtime.Version())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if err := readInConfig(v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if peers != nil && len(*peers) > 0 {
|
||||
for i := range *peers {
|
||||
v.SetDefault(cfgPeers+"."+strconv.Itoa(i)+".address", (*peers)[i])
|
||||
v.SetDefault(cfgPeers+"."+strconv.Itoa(i)+".weight", 1)
|
||||
v.SetDefault(cfgPeers+"."+strconv.Itoa(i)+".priority", 1)
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func readInConfig(v *viper.Viper) error {
|
||||
if v.IsSet(cmdConfig) {
|
||||
if err := readConfig(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v.IsSet(cmdConfigDir) {
|
||||
if err := readConfigDir(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readConfigDir(v *viper.Viper) error {
|
||||
cfgSubConfigDir := v.GetString(cmdConfigDir)
|
||||
entries, err := os.ReadDir(cfgSubConfigDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
ext := path.Ext(entry.Name())
|
||||
if ext != ".yaml" && ext != ".yml" {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = mergeConfig(v, path.Join(cfgSubConfigDir, entry.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readConfig(v *viper.Viper) error {
|
||||
for _, fileName := range v.GetStringSlice(cmdConfig) {
|
||||
if err := mergeConfig(v, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeConfig(v *viper.Viper, fileName string) error {
|
||||
cfgFile, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if errClose := cfgFile.Close(); errClose != nil {
|
||||
panic(errClose)
|
||||
}
|
||||
}()
|
||||
|
||||
return v.MergeConfig(cfgFile)
|
||||
}
|
||||
|
||||
// newLogger constructs a zap.Logger instance for current application.
|
||||
// Panics on failure.
|
||||
//
|
||||
// Logger is built from zap's production logging configuration with:
|
||||
// - parameterized level (debug by default)
|
||||
// - console encoding
|
||||
// - ISO8601 time encoding
|
||||
//
|
||||
// Logger records a stack trace for all messages at or above fatal level.
|
||||
//
|
||||
// See also zapcore.Level, zap.NewProductionConfig, zap.AddStacktrace.
|
||||
func newLogger(v *viper.Viper) (*zap.Logger, zap.AtomicLevel) {
|
||||
lvl, err := getLogLevel(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c := zap.NewProductionConfig()
|
||||
c.Level = zap.NewAtomicLevelAt(lvl)
|
||||
c.Encoding = "console"
|
||||
c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
|
||||
l, err := c.Build(
|
||||
zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)),
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("build zap logger instance: %v", err))
|
||||
}
|
||||
|
||||
return l, c.Level
|
||||
}
|
||||
|
||||
func getLogLevel(v *viper.Viper) (zapcore.Level, error) {
|
||||
var lvl zapcore.Level
|
||||
lvlStr := v.GetString(cfgLoggerLevel)
|
||||
err := lvl.UnmarshalText([]byte(lvlStr))
|
||||
if err != nil {
|
||||
return lvl, fmt.Errorf("incorrect logger level configuration %s (%v), "+
|
||||
"value should be one of %v", lvlStr, err, [...]zapcore.Level{
|
||||
zapcore.DebugLevel,
|
||||
zapcore.InfoLevel,
|
||||
zapcore.WarnLevel,
|
||||
zapcore.ErrorLevel,
|
||||
zapcore.DPanicLevel,
|
||||
zapcore.PanicLevel,
|
||||
zapcore.FatalLevel,
|
||||
})
|
||||
}
|
||||
return lvl, nil
|
||||
}
|
||||
|
||||
func fetchServers(v *viper.Viper) []ServerInfo {
|
||||
var servers []ServerInfo
|
||||
|
||||
for i := 0; ; i++ {
|
||||
key := cfgServer + "." + strconv.Itoa(i) + "."
|
||||
|
||||
var serverInfo ServerInfo
|
||||
serverInfo.Address = v.GetString(key + "address")
|
||||
serverInfo.TLS.Enabled = v.GetBool(key + cfgTLSEnabled)
|
||||
serverInfo.TLS.KeyFile = v.GetString(key + cfgTLSKeyFile)
|
||||
serverInfo.TLS.CertFile = v.GetString(key + cfgTLSCertFile)
|
||||
|
||||
if serverInfo.Address == "" {
|
||||
break
|
||||
}
|
||||
|
||||
servers = append(servers, serverInfo)
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
func getPools(ctx context.Context, logger *zap.Logger, cfg *viper.Viper) (*pool.Pool, *treepool.Pool, *keys.PrivateKey) {
|
||||
key, err := getFrostFSKey(cfg, logger)
|
||||
if err != nil {
|
||||
logger.Fatal(logs.CouldNotLoadFrostFSPrivateKey, zap.Error(err))
|
||||
}
|
||||
|
||||
var prm pool.InitParameters
|
||||
var prmTree treepool.InitParameters
|
||||
|
||||
prm.SetKey(&key.PrivateKey)
|
||||
prmTree.SetKey(key)
|
||||
logger.Info(logs.UsingCredentials, zap.String("FrostFS", hex.EncodeToString(key.PublicKey().Bytes())))
|
||||
|
||||
for _, peer := range fetchPeers(logger, cfg) {
|
||||
prm.AddNode(peer)
|
||||
prmTree.AddNode(peer)
|
||||
}
|
||||
|
||||
connTimeout := cfg.GetDuration(cfgConTimeout)
|
||||
if connTimeout <= 0 {
|
||||
connTimeout = defaultConnectTimeout
|
||||
}
|
||||
prm.SetNodeDialTimeout(connTimeout)
|
||||
prmTree.SetNodeDialTimeout(connTimeout)
|
||||
|
||||
streamTimeout := cfg.GetDuration(cfgStreamTimeout)
|
||||
if streamTimeout <= 0 {
|
||||
streamTimeout = defaultStreamTimeout
|
||||
}
|
||||
prm.SetNodeStreamTimeout(streamTimeout)
|
||||
prmTree.SetNodeStreamTimeout(streamTimeout)
|
||||
|
||||
healthCheckTimeout := cfg.GetDuration(cfgReqTimeout)
|
||||
if healthCheckTimeout <= 0 {
|
||||
healthCheckTimeout = defaultRequestTimeout
|
||||
}
|
||||
prm.SetHealthcheckTimeout(healthCheckTimeout)
|
||||
prmTree.SetHealthcheckTimeout(healthCheckTimeout)
|
||||
|
||||
rebalanceInterval := cfg.GetDuration(cfgRebalance)
|
||||
if rebalanceInterval <= 0 {
|
||||
rebalanceInterval = defaultRebalanceTimer
|
||||
}
|
||||
prm.SetClientRebalanceInterval(rebalanceInterval)
|
||||
prmTree.SetClientRebalanceInterval(rebalanceInterval)
|
||||
|
||||
errorThreshold := cfg.GetUint32(cfgPoolErrorThreshold)
|
||||
if errorThreshold <= 0 {
|
||||
errorThreshold = defaultPoolErrorThreshold
|
||||
}
|
||||
prm.SetErrorThreshold(errorThreshold)
|
||||
prm.SetLogger(logger)
|
||||
prmTree.SetLogger(logger)
|
||||
|
||||
var apiGRPCDialOpts []grpc.DialOption
|
||||
var treeGRPCDialOpts []grpc.DialOption
|
||||
if cfg.GetBool(cfgTracingEnabled) {
|
||||
interceptors := []grpc.DialOption{
|
||||
grpc.WithUnaryInterceptor(grpctracing.NewUnaryClientInteceptor()),
|
||||
grpc.WithStreamInterceptor(grpctracing.NewStreamClientInterceptor()),
|
||||
}
|
||||
treeGRPCDialOpts = append(treeGRPCDialOpts, interceptors...)
|
||||
apiGRPCDialOpts = append(apiGRPCDialOpts, interceptors...)
|
||||
}
|
||||
prm.SetGRPCDialOptions(apiGRPCDialOpts...)
|
||||
prmTree.SetGRPCDialOptions(treeGRPCDialOpts...)
|
||||
|
||||
p, err := pool.NewPool(prm)
|
||||
if err != nil {
|
||||
logger.Fatal(logs.FailedToCreateConnectionPool, zap.Error(err))
|
||||
}
|
||||
|
||||
if err = p.Dial(ctx); err != nil {
|
||||
logger.Fatal(logs.FailedToDialConnectionPool, zap.Error(err))
|
||||
}
|
||||
|
||||
treePool, err := treepool.NewPool(prmTree)
|
||||
if err != nil {
|
||||
logger.Fatal(logs.FailedToCreateTreePool, zap.Error(err))
|
||||
}
|
||||
if err = treePool.Dial(ctx); err != nil {
|
||||
logger.Fatal(logs.FailedToDialTreePool, zap.Error(err))
|
||||
}
|
||||
|
||||
return p, treePool, key
|
||||
}
|
||||
|
||||
func fetchPeers(l *zap.Logger, v *viper.Viper) []pool.NodeParam {
|
||||
var nodes []pool.NodeParam
|
||||
for i := 0; ; i++ {
|
||||
key := cfgPeers + "." + strconv.Itoa(i) + "."
|
||||
address := v.GetString(key + "address")
|
||||
weight := v.GetFloat64(key + "weight")
|
||||
priority := v.GetInt(key + "priority")
|
||||
|
||||
if address == "" {
|
||||
break
|
||||
}
|
||||
if weight <= 0 { // unspecified or wrong
|
||||
weight = 1
|
||||
}
|
||||
if priority <= 0 { // unspecified or wrong
|
||||
priority = 1
|
||||
}
|
||||
|
||||
nodes = append(nodes, pool.NewNodeParam(priority, address, weight))
|
||||
|
||||
l.Info(logs.AddedStoragePeer,
|
||||
zap.Int("priority", priority),
|
||||
zap.String("address", address),
|
||||
zap.Float64("weight", weight))
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func fetchSoftMemoryLimit(cfg *viper.Viper) int64 {
|
||||
softMemoryLimit := cfg.GetSizeInBytes(cfgSoftMemoryLimit)
|
||||
if softMemoryLimit <= 0 {
|
||||
softMemoryLimit = defaultSoftMemoryLimit
|
||||
}
|
||||
|
||||
return int64(softMemoryLimit)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue