From 0fb5c51ac9bb9f877dfb31b1c533ad41939711ee Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Tue, 20 Sep 2022 16:20:45 +0300 Subject: [PATCH] [#1764] neofs-node: Validate config before usage Signed-off-by: Evgenii Stratonikov --- CHANGELOG.md | 1 + cmd/neofs-node/config.go | 8 +-- cmd/neofs-node/main.go | 8 ++- cmd/neofs-node/validate.go | 91 +++++++++++++++++++++++++++++++++ cmd/neofs-node/validate_test.go | 38 ++++++++++++++ config/mainnet/config.yml | 12 +++-- config/testnet/config.yml | 10 ++-- 7 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 cmd/neofs-node/validate.go create mode 100644 cmd/neofs-node/validate_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a1435fc4..e048ff093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Changelog for NeoFS Node ### Added - Changelog updates CI step (#1808) +- Validate storage node configuration before node startup (#1805) ### Changed diff --git a/cmd/neofs-node/config.go b/cmd/neofs-node/config.go index d2f868d79..da027bf5e 100644 --- a/cmd/neofs-node/config.go +++ b/cmd/neofs-node/config.go @@ -240,13 +240,7 @@ type cfgReputation struct { var persistateSideChainLastBlockKey = []byte("side_chain_last_processed_block") -func initCfg(path string) *cfg { - var p config.Prm - - appCfg := config.New(p, - config.WithConfigFile(path), - ) - +func initCfg(appCfg *config.Config) *cfg { key := nodeconfig.Key(appCfg) var logPrm logger.Prm diff --git a/cmd/neofs-node/main.go b/cmd/neofs-node/main.go index 92a2f4c13..28bc32083 100644 --- a/cmd/neofs-node/main.go +++ b/cmd/neofs-node/main.go @@ -9,6 +9,7 @@ import ( "os/signal" "syscall" + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config" "github.com/nspcc-dev/neofs-node/misc" "github.com/nspcc-dev/neofs-node/pkg/services/control" "go.uber.org/zap" @@ -44,7 +45,12 @@ func main() { os.Exit(SuccessReturnCode) } - c := initCfg(*configFile) + appCfg := config.New(config.Prm{}, config.WithConfigFile(*configFile)) + + err := validateConfig(appCfg) + fatalOnErr(err) + + c := initCfg(appCfg) initApp(c) diff --git a/cmd/neofs-node/validate.go b/cmd/neofs-node/validate.go new file mode 100644 index 000000000..6a0640f9a --- /dev/null +++ b/cmd/neofs-node/validate.go @@ -0,0 +1,91 @@ +package main + +import ( + "fmt" + "path/filepath" + + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config" + engineconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine" + shardconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard" + treeconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/tree" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/blobovniczatree" + "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/fstree" +) + +// validateConfig validates storage node configuration. +func validateConfig(c *config.Config) error { + shardNum := 0 + paths := make(map[string]pathDescription) + return engineconfig.IterateShards(c, false, func(sc *shardconfig.Config) error { + if sc.WriteCache().Enabled() { + err := addPath(paths, "writecache", shardNum, sc.WriteCache().Path()) + if err != nil { + return err + } + } + + if err := addPath(paths, "metabase", shardNum, sc.Metabase().Path()); err != nil { + return err + } + + treeConfig := treeconfig.Tree(c) + if treeConfig.Enabled() { + err := addPath(paths, "pilorama", shardNum, sc.Pilorama().Path()) + if err != nil { + return err + } + } + + blobstor := sc.BlobStor().Storages() + if len(blobstor) != 2 { + // TODO (@fyrcik): remove after #1522 + return fmt.Errorf("blobstor section must have 2 components, got: %d", len(blobstor)) + } + for i := range blobstor { + switch blobstor[i].Type() { + case fstree.Type, blobovniczatree.Type: + default: + // FIXME #1764 (@fyrchik): this line is currently unreachable, + // because we panic in `sc.BlobStor().Storages()`. + return fmt.Errorf("unexpected storage type: %s (shard %d)", + blobstor[i].Type(), shardNum) + } + if blobstor[i].Perm()&0600 != 0600 { + return fmt.Errorf("invalid permissions for blobstor component: %s, "+ + "expected at least rw- for the owner (shard %d)", + blobstor[i].Perm(), shardNum) + } + if blobstor[i].Path() == "" { + return fmt.Errorf("blobstor component path is empty (shard %d)", shardNum) + } + err := addPath(paths, fmt.Sprintf("blobstor[%d]", i), shardNum, blobstor[i].Path()) + if err != nil { + return err + } + } + + shardNum++ + return nil + }) +} + +type pathDescription struct { + shard int + component string +} + +func addPath(paths map[string]pathDescription, component string, shard int, path string) error { + if path == "" { + return fmt.Errorf("%s at shard %d has empty path", component, shard) + } + + path = filepath.Clean(path) + c, ok := paths[path] + if ok { + return fmt.Errorf("%s at shard %d and %s at shard %d have the same paths: %s", + c.component, c.shard, component, shard, path) + } + + paths[path] = pathDescription{shard: shard, component: component} + return nil +} diff --git a/cmd/neofs-node/validate_test.go b/cmd/neofs-node/validate_test.go new file mode 100644 index 000000000..44f51dbd3 --- /dev/null +++ b/cmd/neofs-node/validate_test.go @@ -0,0 +1,38 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config" + configtest "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/test" + "github.com/stretchr/testify/require" +) + +func TestValidate(t *testing.T) { + const exampleConfigPrefix = "../../config/" + t.Run("examples", func(t *testing.T) { + p := filepath.Join(exampleConfigPrefix, "example/node") + configtest.ForEachFileType(p, func(c *config.Config) { + var err error + require.NotPanics(t, func() { + err = validateConfig(c) + }) + require.NoError(t, err) + }) + }) + + t.Run("mainnet", func(t *testing.T) { + os.Clearenv() // ENVs have priority over config files, so we do this in tests + p := filepath.Join(exampleConfigPrefix, "mainnet/config.yml") + c := config.New(config.Prm{}, config.WithConfigFile(p)) + require.NoError(t, validateConfig(c)) + }) + t.Run("testnet", func(t *testing.T) { + os.Clearenv() // ENVs have priority over config files, so we do this in tests + p := filepath.Join(exampleConfigPrefix, "testnet/config.yml") + c := config.New(config.Prm{}, config.WithConfigFile(p)) + require.NoError(t, validateConfig(c)) + }) +} diff --git a/config/mainnet/config.yml b/config/mainnet/config.yml index 8ea74bf6e..71b601577 100644 --- a/config/mainnet/config.yml +++ b/config/mainnet/config.yml @@ -21,15 +21,19 @@ storage: shard: 0: metabase: - path: /metabase + path: /storage/path/metabase perm: 0600 blobstor: - path: /blobstor - perm: 0600 - blobovnicza: + - path: /storage/path/blobovnicza + type: blobovnicza + perm: 0600 opened_cache_capacity: 32 depth: 1 width: 1 + - path: /storage/path/fstree + type: fstree + perm: 0600 + depth: 4 writecache: enabled: false gc: diff --git a/config/testnet/config.yml b/config/testnet/config.yml index e74add647..55e1997bf 100644 --- a/config/testnet/config.yml +++ b/config/testnet/config.yml @@ -36,12 +36,16 @@ storage: path: /storage/metabase perm: 0777 blobstor: - path: /storage/blobstor - perm: 0777 - blobovnicza: + - path: /storage/path/blobovnicza + type: blobovnicza + perm: 0600 opened_cache_capacity: 32 depth: 1 width: 1 + - path: /storage/path/fstree + type: fstree + perm: 0600 + depth: 4 writecache: enabled: false gc: