Skip to content

Commit

Permalink
cmd: add tool to migrate objects from Peapod to FSTree
Browse files Browse the repository at this point in the history
Add `cmd/peapod-to-fstree` application which accepts YAML
configuration file of the storage node and, for each configured shard,
overtakes data from Peapod to FSTree that has already been configured or created
in the parent directory if it is not configured.

The tool is going to be used for phased and safe rejection of the
Peapod and the transition to FSTree.

Closes #2924.

Signed-off-by: Andrey Butusov <andrey@nspcc.io>
  • Loading branch information
End-rey committed Nov 13, 2024
1 parent 627f16e commit b189b3f
Show file tree
Hide file tree
Showing 2 changed files with 310 additions and 0 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ attribute, which is used for container domain name in NNS contracts (#2954)
- Docs files for cli commands to the `docs/cli-commands` folder (#2983)
- `logger.encoding` config option (#2999)
- Reloading morph endpoints with SIGHUP (#2998)
- New `peapod-to-fstree` tool providing peapod-to-fstree data migration (#3013)

### Fixed
- Do not search for tombstones when handling their expiration, use local indexes instead (#2929)
Expand All @@ -41,6 +42,7 @@ attribute, which is used for container domain name in NNS contracts (#2954)
- Pprof and metrics services stop at the end of SN's application lifecycle (#2976)
- Reject configuration with unknown fields (#2981)
- Log sampling is disabled by default now (#3011)
- Deprecate peapod substorage (#3013)

### Removed
- Support for node.key configuration (#2959)
Expand All @@ -59,6 +61,23 @@ introduced in version 0.22.3 and support for binary keys was removed from
other components in 0.33.0 and 0.37.0. Please migrate to wallets (see 0.37.0
notes) if you've not done it previously.

To migrate data from Peapods to FSTree:
```shell
$ peapod-to-fstree -config </path/to/storage/node/config>
```
For any shard, the data from the configured Peapod is copied into
a FSTree that must be already configured, if it is not, into
a created FSTree dir named `blobstore` in the directory where the tree is
located. For example, `/neofs/data/peapod.db` -> `/neofs/data/blobstore`.
Notice that existing Peapod are untouched. Configuration is also
updated, for example, `/etc/neofs/config.yaml` -> `/etc/neofs/config_fstree.yaml`.
WARN: carefully review the updated config before using it in the application!

Now there is an FSTree in which files of any size will be more efficient and easier to store.
Use only `fstree` sub-storage in `blobstor` config section.
If storage node already stores some data, don't forget to make data migration
described above.

## [0.43.0] - 2024-08-20 - Jukdo

### Added
Expand Down
291 changes: 291 additions & 0 deletions cmd/peapod-to-fstree/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
package main

import (
"errors"
"flag"
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"slices"
"strings"

"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"
fstreeconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard/blobstor/fstree"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/common"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/compression"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/fstree"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/peapod"
"gopkg.in/yaml.v3"
)

func main() {
nodeCfgPath := flag.String("config", "", "Path to storage node's YAML configuration file")

flag.Parse()

if *nodeCfgPath == "" {
log.Fatal("missing storage node config flag")
}

Check warning on line 32 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L25-L32

Added lines #L25 - L32 were not covered by tests

appCfg := config.New(config.Prm{}, config.WithConfigFile(*nodeCfgPath))

err := engineconfig.IterateShards(appCfg, false, func(sc *shardconfig.Config) error {
log.Println("processing shard...")

var ppd, fstr common.Storage
var ppdPerm fs.FileMode
storagesCfg := sc.BlobStor().Storages()

for i := range storagesCfg {
switch storagesCfg[i].Type() {
case fstree.Type:
sub := fstreeconfig.From((*config.Config)(storagesCfg[i]))

fstr = fstree.New(
fstree.WithPath(storagesCfg[i].Path()),
fstree.WithPerm(storagesCfg[i].Perm()),
fstree.WithDepth(sub.Depth()),
fstree.WithNoSync(sub.NoSync()),
fstree.WithCombinedCountLimit(sub.CombinedCountLimit()),
fstree.WithCombinedSizeLimit(sub.CombinedSizeLimit()),
fstree.WithCombinedSizeThreshold(sub.CombinedSizeThreshold()),
fstree.WithCombinedWriteInterval(storagesCfg[i].FlushInterval()),
)

Check warning on line 57 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L34-L57

Added lines #L34 - L57 were not covered by tests

case peapod.Type:
ppdPerm = storagesCfg[i].Perm()
ppd = peapod.New(storagesCfg[i].Path(), ppdPerm, storagesCfg[i].FlushInterval())
default:
return fmt.Errorf("invalid storage type: %s", storagesCfg[i].Type())

Check warning on line 63 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L59-L63

Added lines #L59 - L63 were not covered by tests
}
}

if ppd == nil {
log.Println("Peapod is not configured for the current shard, going to next one...")
return nil
}

Check warning on line 70 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L67-L70

Added lines #L67 - L70 were not covered by tests

ppdPath := ppd.Path()
if !filepath.IsAbs(ppdPath) {
log.Fatalf("Peapod path '%s' is not absolute, make it like this in the config file first\n", ppdPath)
}

Check warning on line 75 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L72-L75

Added lines #L72 - L75 were not covered by tests

if fstr == nil {
fstr = fstree.New(
fstree.WithPath(filepath.Join(filepath.Dir(ppdPath), "blobstore")),
fstree.WithPerm(ppdPerm),
)
}

Check warning on line 82 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L77-L82

Added lines #L77 - L82 were not covered by tests

var compressCfg compression.Config
compressCfg.Enabled = sc.Compress()
compressCfg.UncompressableContentTypes = sc.UncompressableContentTypes()

err := compressCfg.Init()
if err != nil {
log.Fatal("init compression config for the current shard: ", err)
}

Check warning on line 91 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L84-L91

Added lines #L84 - L91 were not covered by tests

ppd.SetCompressor(&compressCfg)
fstr.SetCompressor(&compressCfg)

log.Printf("migrating data from Peapod '%s' to Fstree '%s'...\n", ppd.Path(), fstr.Path())

err = common.Copy(fstr, ppd)
if err != nil {
log.Fatal("migration failed: ", err)
}

Check warning on line 101 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L93-L101

Added lines #L93 - L101 were not covered by tests

log.Println("data successfully migrated in the current shard, going to the next one...")

return nil

Check warning on line 105 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L103-L105

Added lines #L103 - L105 were not covered by tests
})
if err != nil {
log.Fatal(err)
}

Check warning on line 109 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L107-L109

Added lines #L107 - L109 were not covered by tests

srcPath := *nodeCfgPath
ss := strings.Split(srcPath, ".")
ss[0] += "_fstree"

dstPath := strings.Join(ss, ".")

log.Printf("data successfully migrated in all shards, migrating configuration to '%s' file...\n", dstPath)

err = migrateConfigToFstree(dstPath, srcPath)
if err != nil {
log.Fatal(err)
}

Check warning on line 122 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L111-L122

Added lines #L111 - L122 were not covered by tests
}

func migrateConfigToFstree(dstPath, srcPath string) error {
fData, err := os.ReadFile(srcPath)
if err != nil {
return fmt.Errorf("read source config config file: %w", err)
}

Check warning on line 129 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L125-L129

Added lines #L125 - L129 were not covered by tests

var mConfig map[any]any

err = yaml.Unmarshal(fData, &mConfig)
if err != nil {
return fmt.Errorf("decode config from YAML: %w", err)
}

Check warning on line 136 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L131-L136

Added lines #L131 - L136 were not covered by tests

v, ok := mConfig["storage"]
if !ok {
return errors.New("missing 'storage' section")
}

Check warning on line 141 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L138-L141

Added lines #L138 - L141 were not covered by tests

mStorage, ok := v.(map[string]any)
if !ok {
return fmt.Errorf("unexpected 'storage' section type: %T instead of %T", v, mStorage)
}

Check warning on line 146 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L143-L146

Added lines #L143 - L146 were not covered by tests

v, ok = mStorage["shard"]
if !ok {
return errors.New("missing 'storage.shard' section")
}

Check warning on line 151 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L148-L151

Added lines #L148 - L151 were not covered by tests

mShards, ok := v.(map[any]any)
if !ok {
return fmt.Errorf("unexpected 'storage.shard' section type: %T instead of %T", v, mShards)
}

Check warning on line 156 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L153-L156

Added lines #L153 - L156 were not covered by tests

replacePeapodWithFstree := func(mShard map[string]any, shardDesc any) error {
v, ok := mShard["blobstor"]
if !ok {
return fmt.Errorf("missing 'blobstor' section in shard '%v' config", shardDesc)
}

Check warning on line 162 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L158-L162

Added lines #L158 - L162 were not covered by tests

sBlobStor, ok := v.([]any)
if !ok {
return fmt.Errorf("unexpected 'blobstor' section type in shard '%v': %T instead of %T", shardDesc, v, sBlobStor)
}

Check warning on line 167 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L164-L167

Added lines #L164 - L167 were not covered by tests

var ppdSubStorage map[string]any
var ppdSubStorageIndex int
var fstreeExist bool

for i := range sBlobStor {
mSubStorage, ok := sBlobStor[i].(map[string]any)
if !ok {
return fmt.Errorf("unexpected sub-storage #%d type in shard '%v': %T instead of %T", i, shardDesc, v, mStorage)
}

Check warning on line 177 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L169-L177

Added lines #L169 - L177 were not covered by tests

v, ok := mSubStorage["type"]
if ok {
typ, ok := v.(string)
if !ok {
return fmt.Errorf("unexpected type of sub-storage name: %T instead of %T", v, typ)
}

Check warning on line 184 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L179-L184

Added lines #L179 - L184 were not covered by tests

if typ == peapod.Type {
ppdSubStorage = mSubStorage
ppdSubStorageIndex = i
}

Check warning on line 189 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L186-L189

Added lines #L186 - L189 were not covered by tests

if typ == fstree.Type {
fstreeExist = true
}

Check warning on line 193 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L191-L193

Added lines #L191 - L193 were not covered by tests

continue

Check warning on line 195 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L195

Added line #L195 was not covered by tests
}

// in 'default' section 'type' may be missing

_, withDepth := mSubStorage["depth"]
_, withNoSync := mSubStorage["no_sync"]
_, withCountLimit := mSubStorage["combined_count_limit"]
_, withSizeLimit := mSubStorage["combined_size_limit"]
_, withSizeThreshold := mSubStorage["combined_size_threshold"]

if withDepth || withNoSync || withCountLimit || withSizeLimit || withSizeThreshold {
fstreeExist = true
} else {
ppdSubStorage = mSubStorage
ppdSubStorageIndex = i
}

Check warning on line 211 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L200-L211

Added lines #L200 - L211 were not covered by tests
}

if ppdSubStorage == nil {
log.Printf("peapod is not configured for the shard '%v', skip\n", shardDesc)
return nil
}

Check warning on line 217 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L214-L217

Added lines #L214 - L217 were not covered by tests

if fstreeExist {
mShard["blobstor"] = slices.Delete(sBlobStor, ppdSubStorageIndex, ppdSubStorageIndex+1)
return nil
}

Check warning on line 222 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L219-L222

Added lines #L219 - L222 were not covered by tests

for k := range ppdSubStorage {
switch k {
default:
delete(ppdSubStorage, k)
case "type", "path", "perm":

Check warning on line 228 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L224-L228

Added lines #L224 - L228 were not covered by tests
}
}

ppdSubStorage["type"] = fstree.Type

v, ok = ppdSubStorage["path"]
if ok {
path, ok := v.(string)
if !ok {
return fmt.Errorf("unexpected sub-storage path type: %T instead of %T", v, path)
}

Check warning on line 239 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L232-L239

Added lines #L232 - L239 were not covered by tests

ppdSubStorage["path"] = filepath.Join(filepath.Dir(path), fmt.Sprintf("blobstore%v", shardDesc))

Check warning on line 241 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L241

Added line #L241 was not covered by tests
}

return nil

Check warning on line 244 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L244

Added line #L244 was not covered by tests
}

v, ok = mShards["default"]
if ok {
mShard, ok := v.(map[string]any)
if !ok {
return fmt.Errorf("unexpected 'storage.shard.default' section type: %T instead of %T", v, mShard)
}

Check warning on line 252 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L247-L252

Added lines #L247 - L252 were not covered by tests

err = replacePeapodWithFstree(mShard, "default")
if err != nil {
return err
}

Check warning on line 257 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L254-L257

Added lines #L254 - L257 were not covered by tests
}

for i := 0; ; i++ {
v, ok = mShards[i]
if !ok {
if i == 0 {
return errors.New("missing numbered shards")
}
break

Check warning on line 266 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L260-L266

Added lines #L260 - L266 were not covered by tests
}

mShard, ok := v.(map[string]any)
if !ok {
return fmt.Errorf("unexpected 'storage.shard.%d' section type: %T instead of %T", i, v, mStorage)
}

Check warning on line 272 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L269-L272

Added lines #L269 - L272 were not covered by tests

err = replacePeapodWithFstree(mShard, i)
if err != nil {
return err
}

Check warning on line 277 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L274-L277

Added lines #L274 - L277 were not covered by tests
}

data, err := yaml.Marshal(mConfig)
if err != nil {
return fmt.Errorf("encode modified config into YAML: %w", err)
}

Check warning on line 283 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L280-L283

Added lines #L280 - L283 were not covered by tests

err = os.WriteFile(dstPath, data, 0o640)
if err != nil {
return fmt.Errorf("write resulting config to the destination file: %w", err)
}

Check warning on line 288 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L285-L288

Added lines #L285 - L288 were not covered by tests

return nil

Check warning on line 290 in cmd/peapod-to-fstree/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/peapod-to-fstree/main.go#L290

Added line #L290 was not covered by tests
}

0 comments on commit b189b3f

Please sign in to comment.