2020-08-21 15:01:59 +00:00
|
|
|
package main
|
|
|
|
|
2020-08-22 11:03:45 +00:00
|
|
|
import (
|
|
|
|
"context"
|
2020-08-22 14:17:03 +00:00
|
|
|
"crypto/ecdsa"
|
2020-08-24 09:40:32 +00:00
|
|
|
"net"
|
2020-09-16 07:45:08 +00:00
|
|
|
"strings"
|
2020-08-22 11:03:45 +00:00
|
|
|
"sync"
|
2020-10-21 09:26:16 +00:00
|
|
|
"time"
|
2020-08-22 14:17:03 +00:00
|
|
|
|
2020-08-22 15:20:47 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2020-10-08 13:16:00 +00:00
|
|
|
"github.com/nspcc-dev/neofs-api-go/pkg"
|
2020-09-22 12:59:09 +00:00
|
|
|
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
2020-08-22 14:17:03 +00:00
|
|
|
crypto "github.com/nspcc-dev/neofs-crypto"
|
2020-09-16 07:45:08 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/misc"
|
2020-09-23 13:55:46 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/core/container"
|
|
|
|
netmapCore "github.com/nspcc-dev/neofs-node/pkg/core/netmap"
|
2020-09-24 12:32:30 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket"
|
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket/boltdb"
|
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/bucket/fsbucket"
|
2020-08-22 15:20:47 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/morph/client"
|
2020-10-03 07:46:57 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/morph/client/container/wrapper"
|
2020-09-24 07:46:47 +00:00
|
|
|
nmwrapper "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap/wrapper"
|
2020-10-21 09:26:16 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/morph/event"
|
2020-09-23 13:31:51 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/network"
|
2020-08-24 15:51:42 +00:00
|
|
|
tokenStorage "github.com/nspcc-dev/neofs-node/pkg/services/session/storage"
|
2020-09-25 12:34:17 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/util/logger"
|
2020-10-02 13:18:38 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/util/profiler"
|
2020-09-24 12:32:30 +00:00
|
|
|
"github.com/pkg/errors"
|
2020-09-16 07:45:08 +00:00
|
|
|
"github.com/spf13/viper"
|
2020-09-25 12:34:17 +00:00
|
|
|
"go.uber.org/zap"
|
2020-08-22 15:20:47 +00:00
|
|
|
"google.golang.org/grpc"
|
2020-08-22 11:03:45 +00:00
|
|
|
)
|
|
|
|
|
2020-09-16 07:45:08 +00:00
|
|
|
const (
|
2020-09-25 12:34:17 +00:00
|
|
|
// logger keys
|
|
|
|
cfgLogLevel = "logger.level"
|
|
|
|
cfgLogFormat = "logger.format"
|
|
|
|
cfgLogTrace = "logger.trace_level"
|
|
|
|
cfgLogInitSampling = "logger.sampling.initial"
|
|
|
|
cfgLogThereafterSampling = "logger.sampling.thereafter"
|
|
|
|
|
2020-10-02 13:18:38 +00:00
|
|
|
// pprof keys
|
|
|
|
cfgProfilerEnable = "pprof.enabled"
|
|
|
|
cfgProfilerAddr = "pprof.address"
|
|
|
|
cfgProfilerTTL = "pprof.shutdown_ttl"
|
|
|
|
|
2020-09-16 07:45:08 +00:00
|
|
|
// config keys for cfgNodeInfo
|
2020-09-22 11:02:32 +00:00
|
|
|
cfgNodeKey = "node.key"
|
|
|
|
cfgBootstrapAddress = "node.address"
|
|
|
|
cfgNodeAttributePrefix = "node.attribute"
|
2020-10-05 10:44:28 +00:00
|
|
|
cfgMaxObjectSize = "node.maxobjectsize" // todo: get value from chain
|
2020-09-16 07:45:08 +00:00
|
|
|
|
|
|
|
// config keys for cfgGRPC
|
2020-10-14 20:35:48 +00:00
|
|
|
cfgListenAddress = "grpc.endpoint"
|
|
|
|
cfgMaxMsgSize = "grpc.maxmessagesize"
|
|
|
|
cfgReflectService = "grpc.enable_reflect_service"
|
2020-09-16 07:45:08 +00:00
|
|
|
|
|
|
|
// config keys for cfgMorph
|
|
|
|
cfgMorphRPCAddress = "morph.endpoint"
|
|
|
|
|
2020-10-21 09:26:16 +00:00
|
|
|
cfgMorphNotifyRPCAddress = "morph.notification.endpoint"
|
|
|
|
cfgMorphNotifyDialTimeout = "morph.notification.dial_timeout"
|
|
|
|
|
2020-09-16 07:45:08 +00:00
|
|
|
// config keys for cfgAccounting
|
|
|
|
cfgAccountingContract = "accounting.scripthash"
|
|
|
|
cfgAccountingFee = "accounting.fee"
|
|
|
|
|
|
|
|
// config keys for cfgNetmap
|
|
|
|
cfgNetmapContract = "netmap.scripthash"
|
|
|
|
cfgNetmapFee = "netmap.fee"
|
|
|
|
|
|
|
|
// config keys for cfgContainer
|
|
|
|
cfgContainerContract = "container.scripthash"
|
|
|
|
cfgContainerFee = "container.fee"
|
2020-09-29 12:38:44 +00:00
|
|
|
|
2020-09-24 12:32:30 +00:00
|
|
|
cfgObjectStorage = "storage.object"
|
|
|
|
cfgMetaStorage = "storage.meta"
|
2020-10-05 10:44:28 +00:00
|
|
|
|
|
|
|
cfgGCQueueSize = "gc.queuesize"
|
|
|
|
cfgGCQueueTick = "gc.duration.sleep"
|
|
|
|
cfgGCTimeout = "gc.duration.timeout"
|
2020-10-21 09:28:42 +00:00
|
|
|
|
|
|
|
cfgPolicerWorkScope = "policer.work_scope"
|
|
|
|
cfgPolicerExpRate = "policer.expansion_rate"
|
|
|
|
cfgPolicerHeadTimeout = "policer.head_timeout"
|
2020-09-16 07:45:08 +00:00
|
|
|
)
|
|
|
|
|
2020-10-02 08:01:54 +00:00
|
|
|
const (
|
|
|
|
addressSize = 72 // 32 bytes oid, 32 bytes cid, 8 bytes protobuf encoding
|
|
|
|
)
|
|
|
|
|
2020-08-21 15:01:59 +00:00
|
|
|
type cfg struct {
|
2020-08-22 11:03:45 +00:00
|
|
|
ctx context.Context
|
|
|
|
|
2020-09-16 07:45:08 +00:00
|
|
|
viper *viper.Viper
|
|
|
|
|
2020-09-25 12:34:17 +00:00
|
|
|
log *zap.Logger
|
|
|
|
|
2020-08-22 11:03:45 +00:00
|
|
|
wg *sync.WaitGroup
|
|
|
|
|
2020-08-22 14:17:03 +00:00
|
|
|
key *ecdsa.PrivateKey
|
2020-08-22 15:20:47 +00:00
|
|
|
|
2020-10-08 13:16:00 +00:00
|
|
|
apiVersion *pkg.Version
|
|
|
|
|
2020-08-24 09:40:32 +00:00
|
|
|
cfgGRPC cfgGRPC
|
|
|
|
|
|
|
|
cfgMorph cfgMorph
|
|
|
|
|
|
|
|
cfgAccounting cfgAccounting
|
2020-08-24 14:07:08 +00:00
|
|
|
|
|
|
|
cfgContainer cfgContainer
|
2020-08-24 15:51:42 +00:00
|
|
|
|
2020-08-31 15:19:21 +00:00
|
|
|
cfgNetmap cfgNetmap
|
|
|
|
|
2020-08-24 15:51:42 +00:00
|
|
|
privateTokenStore *tokenStorage.TokenStore
|
2020-08-31 15:19:21 +00:00
|
|
|
|
|
|
|
cfgNodeInfo cfgNodeInfo
|
2020-09-23 13:31:51 +00:00
|
|
|
|
|
|
|
localAddr *network.Address
|
2020-09-23 13:55:46 +00:00
|
|
|
|
|
|
|
cfgObject cfgObject
|
2020-10-02 13:18:38 +00:00
|
|
|
|
|
|
|
profiler profiler.Profiler
|
2020-10-03 09:57:02 +00:00
|
|
|
|
|
|
|
workers []worker
|
2020-08-24 09:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type cfgGRPC struct {
|
|
|
|
listener net.Listener
|
2020-08-22 15:20:47 +00:00
|
|
|
|
2020-08-24 09:40:32 +00:00
|
|
|
server *grpc.Server
|
2020-09-30 08:39:45 +00:00
|
|
|
|
|
|
|
maxChunkSize uint64
|
|
|
|
|
|
|
|
maxAddrAmount uint64
|
2020-10-14 20:35:48 +00:00
|
|
|
|
|
|
|
enableReflectService bool
|
2020-08-24 09:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type cfgMorph struct {
|
|
|
|
client *client.Client
|
|
|
|
}
|
2020-08-22 15:20:47 +00:00
|
|
|
|
2020-08-24 09:40:32 +00:00
|
|
|
type cfgAccounting struct {
|
2020-09-16 07:45:08 +00:00
|
|
|
scriptHash util.Uint160
|
2020-08-22 15:20:47 +00:00
|
|
|
|
2020-08-24 09:40:32 +00:00
|
|
|
fee util.Fixed8
|
2020-08-21 15:01:59 +00:00
|
|
|
}
|
|
|
|
|
2020-08-24 14:07:08 +00:00
|
|
|
type cfgContainer struct {
|
2020-09-16 07:45:08 +00:00
|
|
|
scriptHash util.Uint160
|
2020-08-24 14:07:08 +00:00
|
|
|
|
|
|
|
fee util.Fixed8
|
|
|
|
}
|
|
|
|
|
2020-08-31 15:19:21 +00:00
|
|
|
type cfgNetmap struct {
|
2020-09-16 07:45:08 +00:00
|
|
|
scriptHash util.Uint160
|
2020-09-24 07:46:47 +00:00
|
|
|
wrapper *nmwrapper.Wrapper
|
2020-08-31 15:19:21 +00:00
|
|
|
|
|
|
|
fee util.Fixed8
|
2020-10-21 09:26:16 +00:00
|
|
|
|
|
|
|
parsers map[event.Type]event.Parser
|
|
|
|
|
|
|
|
subscribers map[event.Type][]event.Handler
|
2020-10-21 15:12:31 +00:00
|
|
|
|
|
|
|
state *networkState
|
2020-08-31 15:19:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type BootstrapType uint32
|
|
|
|
|
|
|
|
type cfgNodeInfo struct {
|
2020-10-08 13:17:50 +00:00
|
|
|
// values from config
|
2020-09-22 11:02:32 +00:00
|
|
|
bootType BootstrapType
|
2020-09-22 12:59:09 +00:00
|
|
|
attributes []*netmap.Attribute
|
2020-10-08 13:17:50 +00:00
|
|
|
|
|
|
|
// values at runtime
|
|
|
|
info *netmap.NodeInfo
|
2020-08-31 15:19:21 +00:00
|
|
|
}
|
|
|
|
|
2020-09-23 13:55:46 +00:00
|
|
|
type cfgObject struct {
|
|
|
|
netMapStorage netmapCore.Source
|
|
|
|
|
|
|
|
cnrStorage container.Source
|
2020-09-29 12:38:44 +00:00
|
|
|
|
|
|
|
maxObjectSize uint64
|
2020-09-24 12:32:30 +00:00
|
|
|
|
|
|
|
metastorage bucket.Bucket
|
|
|
|
|
|
|
|
blobstorage bucket.Bucket
|
2020-10-03 07:46:57 +00:00
|
|
|
|
|
|
|
cnrClient *wrapper.Wrapper
|
2020-09-23 13:55:46 +00:00
|
|
|
}
|
|
|
|
|
2020-08-31 15:19:21 +00:00
|
|
|
const (
|
|
|
|
_ BootstrapType = iota
|
|
|
|
StorageNode
|
|
|
|
RelayNode
|
|
|
|
)
|
|
|
|
|
2020-09-16 07:45:08 +00:00
|
|
|
func initCfg(path string) *cfg {
|
|
|
|
viperCfg := initViper(path)
|
|
|
|
|
|
|
|
key, err := crypto.LoadPrivateKey(viperCfg.GetString(cfgNodeKey))
|
|
|
|
fatalOnErr(err)
|
|
|
|
|
|
|
|
u160Accounting, err := util.Uint160DecodeStringLE(
|
|
|
|
viperCfg.GetString(cfgAccountingContract))
|
|
|
|
fatalOnErr(err)
|
|
|
|
|
|
|
|
u160Netmap, err := util.Uint160DecodeStringLE(
|
|
|
|
viperCfg.GetString(cfgNetmapContract))
|
|
|
|
fatalOnErr(err)
|
|
|
|
|
|
|
|
u160Container, err := util.Uint160DecodeStringLE(
|
|
|
|
viperCfg.GetString(cfgContainerContract))
|
2020-08-22 14:17:03 +00:00
|
|
|
fatalOnErr(err)
|
|
|
|
|
2020-09-25 12:34:17 +00:00
|
|
|
log, err := logger.NewLogger(viperCfg)
|
|
|
|
fatalOnErr(err)
|
|
|
|
|
2020-09-24 08:27:14 +00:00
|
|
|
netAddr, err := network.AddressFromString(viperCfg.GetString(cfgBootstrapAddress))
|
2020-09-23 13:31:51 +00:00
|
|
|
fatalOnErr(err)
|
|
|
|
|
2020-10-02 08:01:54 +00:00
|
|
|
maxChunkSize := viperCfg.GetUint64(cfgMaxMsgSize) * 3 / 4 // 25% to meta, 75% to payload
|
|
|
|
maxAddrAmount := maxChunkSize / addressSize // each address is about 72 bytes
|
|
|
|
|
2020-09-24 12:32:30 +00:00
|
|
|
c := &cfg{
|
2020-10-08 13:16:00 +00:00
|
|
|
ctx: context.Background(),
|
|
|
|
viper: viperCfg,
|
|
|
|
log: log,
|
|
|
|
wg: new(sync.WaitGroup),
|
|
|
|
key: key,
|
|
|
|
apiVersion: pkg.SDKVersion(),
|
2020-08-24 09:40:32 +00:00
|
|
|
cfgAccounting: cfgAccounting{
|
2020-09-16 07:45:08 +00:00
|
|
|
scriptHash: u160Accounting,
|
|
|
|
fee: util.Fixed8(viperCfg.GetInt(cfgAccountingFee)),
|
2020-08-22 15:20:47 +00:00
|
|
|
},
|
2020-08-24 14:07:08 +00:00
|
|
|
cfgContainer: cfgContainer{
|
2020-09-16 07:45:08 +00:00
|
|
|
scriptHash: u160Container,
|
|
|
|
fee: util.Fixed8(viperCfg.GetInt(cfgContainerFee)),
|
2020-08-24 14:07:08 +00:00
|
|
|
},
|
2020-08-31 15:19:21 +00:00
|
|
|
cfgNetmap: cfgNetmap{
|
2020-09-16 07:45:08 +00:00
|
|
|
scriptHash: u160Netmap,
|
|
|
|
fee: util.Fixed8(viperCfg.GetInt(cfgNetmapFee)),
|
2020-08-31 15:19:21 +00:00
|
|
|
},
|
|
|
|
cfgNodeInfo: cfgNodeInfo{
|
2020-09-22 11:02:32 +00:00
|
|
|
bootType: StorageNode,
|
2020-09-22 12:59:09 +00:00
|
|
|
attributes: parseAttributes(viperCfg),
|
2020-08-31 15:19:21 +00:00
|
|
|
},
|
2020-09-29 12:38:44 +00:00
|
|
|
cfgObject: cfgObject{
|
|
|
|
maxObjectSize: viperCfg.GetUint64(cfgMaxObjectSize),
|
|
|
|
},
|
2020-09-30 08:39:45 +00:00
|
|
|
cfgGRPC: cfgGRPC{
|
2020-10-14 20:35:48 +00:00
|
|
|
maxChunkSize: maxChunkSize,
|
|
|
|
maxAddrAmount: maxAddrAmount,
|
|
|
|
enableReflectService: viperCfg.GetBool(cfgReflectService),
|
2020-09-30 08:39:45 +00:00
|
|
|
},
|
2020-09-23 13:31:51 +00:00
|
|
|
localAddr: netAddr,
|
2020-08-21 15:01:59 +00:00
|
|
|
}
|
2020-09-24 12:32:30 +00:00
|
|
|
|
|
|
|
initLocalStorage(c)
|
|
|
|
|
|
|
|
return c
|
2020-08-21 15:01:59 +00:00
|
|
|
}
|
2020-09-16 07:45:08 +00:00
|
|
|
|
|
|
|
func initViper(path string) *viper.Viper {
|
|
|
|
v := viper.New()
|
|
|
|
|
|
|
|
v.SetEnvPrefix(misc.Prefix)
|
|
|
|
v.AutomaticEnv()
|
|
|
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
|
|
|
|
|
|
v.SetDefault("app.name", misc.NodeName)
|
|
|
|
v.SetDefault("app.version", misc.Version)
|
|
|
|
|
|
|
|
defaultConfiguration(v)
|
|
|
|
|
|
|
|
if path != "" {
|
|
|
|
v.SetConfigFile(path)
|
|
|
|
v.SetConfigType("yml")
|
|
|
|
fatalOnErr(v.ReadInConfig())
|
|
|
|
}
|
|
|
|
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultConfiguration(v *viper.Viper) {
|
|
|
|
// fixme: all hardcoded private keys must be removed
|
|
|
|
v.SetDefault(cfgNodeKey, "Kwk6k2eC3L3QuPvD8aiaNyoSXgQ2YL1bwS5CP1oKoA9waeAze97s")
|
2020-09-29 12:38:44 +00:00
|
|
|
v.SetDefault(cfgBootstrapAddress, "") // address to bootstrap with
|
|
|
|
v.SetDefault(cfgMaxObjectSize, 1024*1024) // default max object size 1 megabyte
|
2020-09-16 07:45:08 +00:00
|
|
|
|
|
|
|
v.SetDefault(cfgMorphRPCAddress, "http://morph_chain.localtest.nspcc.ru:30333/")
|
2020-10-21 09:26:16 +00:00
|
|
|
v.SetDefault(cfgMorphNotifyRPCAddress, "ws://morph_chain:30333/ws")
|
|
|
|
v.SetDefault(cfgMorphNotifyDialTimeout, 5*time.Second)
|
2020-09-16 07:45:08 +00:00
|
|
|
v.SetDefault(cfgListenAddress, "127.0.0.1:50501") // listen address
|
2020-10-02 08:01:54 +00:00
|
|
|
v.SetDefault(cfgMaxMsgSize, 4<<20) // transport msg limit 4 MiB
|
2020-09-16 07:45:08 +00:00
|
|
|
|
|
|
|
v.SetDefault(cfgAccountingContract, "1aeefe1d0dfade49740fff779c02cd4a0538ffb1")
|
|
|
|
v.SetDefault(cfgAccountingFee, "1")
|
|
|
|
|
|
|
|
v.SetDefault(cfgContainerContract, "9d2ca84d7fb88213c4baced5a6ed4dc402309039")
|
|
|
|
v.SetDefault(cfgContainerFee, "1")
|
|
|
|
|
|
|
|
v.SetDefault(cfgNetmapContract, "75194459637323ea8837d2afe8225ec74a5658c3")
|
|
|
|
v.SetDefault(cfgNetmapFee, "1")
|
2020-09-25 12:34:17 +00:00
|
|
|
|
2020-09-24 12:32:30 +00:00
|
|
|
v.SetDefault(cfgObjectStorage+".type", "inmemory")
|
|
|
|
v.SetDefault(cfgMetaStorage+".type", "inmemory")
|
|
|
|
|
2020-09-25 12:34:17 +00:00
|
|
|
v.SetDefault(cfgLogLevel, "info")
|
|
|
|
v.SetDefault(cfgLogFormat, "console")
|
|
|
|
v.SetDefault(cfgLogTrace, "fatal")
|
|
|
|
v.SetDefault(cfgLogInitSampling, 1000)
|
|
|
|
v.SetDefault(cfgLogThereafterSampling, 1000)
|
2020-10-02 13:18:38 +00:00
|
|
|
|
|
|
|
v.SetDefault(cfgProfilerEnable, false)
|
|
|
|
v.SetDefault(cfgProfilerAddr, ":6060")
|
|
|
|
v.SetDefault(cfgProfilerTTL, "30s")
|
2020-10-05 10:44:28 +00:00
|
|
|
|
|
|
|
v.SetDefault(cfgGCQueueSize, 1000)
|
|
|
|
v.SetDefault(cfgGCQueueTick, "5s")
|
|
|
|
v.SetDefault(cfgGCTimeout, "5s")
|
2020-10-21 09:28:42 +00:00
|
|
|
|
|
|
|
v.SetDefault(cfgPolicerWorkScope, 100)
|
|
|
|
v.SetDefault(cfgPolicerExpRate, 10) // in %
|
|
|
|
v.SetDefault(cfgPolicerHeadTimeout, 5*time.Second)
|
2020-09-16 07:45:08 +00:00
|
|
|
}
|
2020-09-23 13:31:51 +00:00
|
|
|
|
|
|
|
func (c *cfg) LocalAddress() *network.Address {
|
|
|
|
return c.localAddr
|
|
|
|
}
|
2020-09-24 12:32:30 +00:00
|
|
|
|
|
|
|
func initLocalStorage(c *cfg) {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
c.cfgObject.blobstorage, err = initBucket(cfgObjectStorage, c)
|
|
|
|
fatalOnErr(err)
|
|
|
|
|
|
|
|
c.cfgObject.metastorage, err = initBucket(cfgMetaStorage, c)
|
|
|
|
fatalOnErr(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func initBucket(prefix string, c *cfg) (bucket bucket.Bucket, err error) {
|
|
|
|
const inmemory = "inmemory"
|
|
|
|
|
|
|
|
switch c.viper.GetString(prefix + ".type") {
|
|
|
|
case inmemory:
|
|
|
|
bucket = newBucket()
|
|
|
|
c.log.Info("using in-memory bucket", zap.String("storage", prefix))
|
|
|
|
case boltdb.Name:
|
|
|
|
opts, err := boltdb.NewOptions(prefix, c.viper)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "can't create boltdb opts")
|
|
|
|
}
|
|
|
|
bucket, err = boltdb.NewBucket(&opts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "can't create boltdb bucket")
|
|
|
|
}
|
|
|
|
c.log.Info("using boltdb bucket", zap.String("storage", prefix))
|
|
|
|
case fsbucket.Name:
|
|
|
|
bucket, err = fsbucket.NewBucket(prefix, c.viper)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "can't create fs bucket")
|
|
|
|
}
|
|
|
|
c.log.Info("using filesystem bucket", zap.String("storage", prefix))
|
|
|
|
default:
|
|
|
|
return nil, errors.New("unknown storage type")
|
|
|
|
}
|
|
|
|
|
|
|
|
return bucket, nil
|
|
|
|
}
|