From 47914b8dcb2ed9094fccae69d980aaa439019701 Mon Sep 17 00:00:00 2001 From: Ludovico de Nittis Date: Sun, 3 Apr 2022 15:59:28 +0200 Subject: [PATCH] Add option to regenerate invalid seed indexes (#216) * Add option to regenerate invalid seed indexes Sometimes we don't have the guarantee that a particular seed index is always valid. For example if a seed index is calculated and used in two different occasions, it might happen that in the meantime some of the seed files got corrupted or that they simply changed (if the seed is a writable location). One workaroud is to manually launch `verify-index` and then regenerate the index if that check fails. However this involves additional steps and is potentially slower because we need to verify the entire seed before running the `extract` command. Instead with the new option "--regenerate-invalid-seeds", Desync will automatically regenerate the seed index if the validation step fails. Signed-off-by: Ludovico de Nittis * Add the correct seed in the extract test This test usually worked because "blob2.caibx" was considered before "blob2_corrupted.cabix". However this test was expecting to use "blob1.caibx", but that was not the case because in `readSeedDirs()` the index file is skipped if it is what we have set in input. Instead, we add it explicitly to force "blob1.caibx" as a seed. Signed-off-by: Ludovico de Nittis --- assemble.go | 23 +++++++++++--- cmd/desync/extract.go | 30 ++++++++++++------ cmd/desync/extract_test.go | 18 ++++++++++- cmd/desync/testdata/blob1_corrupted_index | 1 + .../testdata/blob1_corrupted_index.caibx | Bin 0 -> 6504 bytes fileseed.go | 24 ++++++++++++++ nullseed.go | 10 ++++++ seed.go | 3 ++ selfseed.go | 10 ++++++ sequencer.go | 12 +++++++ 10 files changed, 116 insertions(+), 15 deletions(-) create mode 120000 cmd/desync/testdata/blob1_corrupted_index create mode 100644 cmd/desync/testdata/blob1_corrupted_index.caibx diff --git a/assemble.go b/assemble.go index e620f9f..83b809c 100644 --- a/assemble.go +++ b/assemble.go @@ -8,13 +8,16 @@ import ( ) // InvalidSeedAction represent the action that we will take if a seed -// happens to be invalid. There are currently two options: either fail with -// an error or skip the invalid seed and try to continue. +// happens to be invalid. There are currently three options: +// - fail with an error +// - skip the invalid seed and try to continue +// - regenerate the invalid seed index type InvalidSeedAction int const ( InvalidSeedActionBailOut InvalidSeedAction = iota InvalidSeedActionSkip + InvalidSeedActionRegenerate ) type AssembleOptions struct { @@ -225,11 +228,21 @@ func AssembleFile(ctx context.Context, name string, idx Index, s Store, seeds [] for { if err := plan.Validate(ctx, options.N); err != nil { // This plan has at least one invalid seed - if options.InvalidSeedAction == InvalidSeedActionBailOut { + switch options.InvalidSeedAction { + case InvalidSeedActionBailOut: return stats, err + case InvalidSeedActionRegenerate: + Log.WithError(err).Info("Unable to use one of the chosen seeds, regenerating it") + if err := seq.RegenerateInvalidSeeds(ctx, options.N); err != nil { + return stats, err + } + case InvalidSeedActionSkip: + // Recreate the plan. This time the seed marked as invalid will be skipped + Log.WithError(err).Info("Unable to use one of the chosen seeds, skipping it") + default: + panic("Unhandled InvalidSeedAction") } - // Skip the invalid seed and try again - Log.WithError(err).Info("Unable to use one of the chosen seeds, skipping it") + seq.Rewind() plan = seq.Plan() continue diff --git a/cmd/desync/extract.go b/cmd/desync/extract.go index bf8bff9..c80bff6 100644 --- a/cmd/desync/extract.go +++ b/cmd/desync/extract.go @@ -14,13 +14,14 @@ import ( type extractOptions struct { cmdStoreOptions - stores []string - cache string - seeds []string - seedDirs []string - inPlace bool - printStats bool - skipInvalidSeeds bool + stores []string + cache string + seeds []string + seedDirs []string + inPlace bool + printStats bool + skipInvalidSeeds bool + regenerateInvalidSeeds bool } func newExtractCommand(ctx context.Context) *cobra.Command { @@ -33,11 +34,15 @@ func newExtractCommand(ctx context.Context) *cobra.Command { When using -k, the blob will be extracted in-place utilizing existing data and the target file will not be deleted on error. This can be used to restart a failed prior extraction without having to retrieve completed chunks again. -Muptiple optional seed indexes can be given with -seed. The matching blob needs +Multiple optional seed indexes can be given with -seed. The matching blob needs to have the same name as the indexfile without the .caibx extension. If several seed files and indexes are available, the -seed-dir option can be used to automatically select call .caibx files in a directory as seeds. Use '-' to read -the index from STDIN.`, +the index from STDIN. If a seed is invalid, by default the extract operation will be +aborted. With the -skip-invalid-seeds, the invalid seeds will be discarded and the +extraction will continue without them. Otherwise with the -regenerate-invalid-seeds, +the eventual invalid seed indexes will be regenerated, in memory, by using the +available data, and neither data nor indexes will be changed on disk.`, Example: ` desync extract -s http://192.168.1.1/ -c /path/to/local file.caibx largefile.bin desync extract -s /mnt/store -s /tmp/other/store file.tar.caibx file.tar desync extract -s /mnt/store --seed /mnt/v1.caibx v2.caibx v2.vmdk`, @@ -52,6 +57,7 @@ the index from STDIN.`, flags.StringSliceVar(&opt.seeds, "seed", nil, "seed indexes") flags.StringSliceVar(&opt.seedDirs, "seed-dir", nil, "directory with seed index files") flags.BoolVar(&opt.skipInvalidSeeds, "skip-invalid-seeds", false, "Skip seeds with invalid chunks") + flags.BoolVar(&opt.regenerateInvalidSeeds, "regenerate-invalid-seeds", false, "Regenerate seed indexes with invalid chunks") flags.StringVarP(&opt.cache, "cache", "c", "", "store to be used as cache") flags.BoolVarP(&opt.inPlace, "in-place", "k", false, "extract the file in place and keep it in case of error") flags.BoolVarP(&opt.printStats, "print-stats", "", false, "print statistics") @@ -75,6 +81,10 @@ func runExtract(ctx context.Context, opt extractOptions, args []string) error { return errors.New("no store provided") } + if opt.skipInvalidSeeds && opt.regenerateInvalidSeeds { + return errors.New("is not possible to use at the same time --skip-invalid-seeds and --regenerate-invalid-seeds") + } + // Parse the store locations, open the stores and add a cache is requested var s desync.Store s, err := MultiStoreWithCache(opt.cmdStoreOptions, opt.cache, opt.stores...) @@ -106,6 +116,8 @@ func runExtract(ctx context.Context, opt extractOptions, args []string) error { invalidSeedAction := desync.InvalidSeedActionBailOut if opt.skipInvalidSeeds { invalidSeedAction = desync.InvalidSeedActionSkip + } else if opt.regenerateInvalidSeeds { + invalidSeedAction = desync.InvalidSeedActionRegenerate } assembleOpt := desync.AssembleOptions{N: opt.n, InvalidSeedAction: invalidSeedAction} diff --git a/cmd/desync/extract_test.go b/cmd/desync/extract_test.go index 20fb902..30c44df 100644 --- a/cmd/desync/extract_test.go +++ b/cmd/desync/extract_test.go @@ -61,13 +61,23 @@ func TestExtractCommand(t *testing.T) { []string{"--store", "testdata/empty.store", "--seed", "testdata/blob2_corrupted.caibx", "--seed", "testdata/blob1.caibx", "--skip-invalid-seeds", "testdata/blob1.caibx"}, out1}, // Here we don't need the `--skip-invalid-seeds` because we expect the blob1 seed to always be the chosen one, being // a 1:1 match with the index that we want to write. So we never reach the point where we validate the corrupted seed. + // Explicitly set blob1 seed because seed-dir skips a seed if it's the same index file we gave in input. {"extract with seed directory without skipping invalid seeds", - []string{"-s", "testdata/blob1.store", "--seed-dir", "testdata", "testdata/blob1.caibx"}, out1}, + []string{"-s", "testdata/blob1.store", "--seed-dir", "testdata", "--seed", "testdata/blob1.caibx", "testdata/blob1.caibx"}, out1}, // Same as above, no need for `--skip-invalid-seeds` {"extract with multiple corrupted seeds", []string{"--store", "testdata/empty.store", "--seed", "testdata/blob2_corrupted.caibx", "--seed", "testdata/blob1.caibx", "testdata/blob1.caibx"}, out1}, {"extract with single seed that has all the expected chunks", []string{"--store", "testdata/empty.store", "--seed", "testdata/blob1.caibx", "testdata/blob1.caibx"}, out1}, + // blob2_corrupted is a corrupted blob that doesn't match its seed index. We regenerate the seed index to match + // this corrupted blob + {"extract while regenerating the corrupted seed", + []string{"--store", "testdata/blob1.store", "--seed", "testdata/blob2_corrupted.caibx", "--regenerate-invalid-seeds", "testdata/blob1.caibx"}, out1}, + // blob1_corrupted_index.caibx is a corrupted seed index that points to a valid blob1 file. By regenerating the + // invalid seed we expect to have an index that is equal to blob1.caibx. That should be enough to do the + // extraction without taking chunks from the store + {"extract with corrupted seed and empty store", + []string{"--store", "testdata/empty.store", "--seed", "testdata/blob1_corrupted_index.caibx", "--regenerate-invalid-seeds", "testdata/blob1.caibx"}, out1}, } { t.Run(test.name, func(t *testing.T) { cmd := newExtractCommand(context.Background()) @@ -125,6 +135,12 @@ func TestExtractWithInvalidSeeds(t *testing.T) { []string{"--store", "testdata/blob1.store", "--seed", "testdata/blob2_corrupted.caibx", "testdata/blob1.caibx"}, out}, {"extract with multiple corrupted seeds", []string{"--store", "testdata/empty.store", "--seed", "testdata/blob2_corrupted.caibx", "--seed", "testdata/blob1.caibx", "testdata/blob2.caibx"}, out}, + {"extract with corrupted blob1 seed and a valid seed", + []string{"--store", "testdata/blob2.store", "--seed", "testdata/blob1_corrupted_index.caibx", "--seed", "testdata/blob1.caibx", "testdata/blob2.caibx"}, out}, + {"extract with corrupted blob1 seed", + []string{"--store", "testdata/blob2.store", "--seed", "testdata/blob1_corrupted_index.caibx", "testdata/blob2.caibx"}, out}, + {"extract with both --regenerate-invalid-seed and --skip-invalid-seeds", + []string{"--store", "testdata/blob1.store", "--seed", "testdata/blob1_corrupted_index.caibx", "--regenerate-invalid-seeds", "--skip-invalid-seeds", "testdata/blob1.caibx"}, out}, } { t.Run(test.name, func(t *testing.T) { cmd := newExtractCommand(context.Background()) diff --git a/cmd/desync/testdata/blob1_corrupted_index b/cmd/desync/testdata/blob1_corrupted_index new file mode 120000 index 0000000..2d69a1c --- /dev/null +++ b/cmd/desync/testdata/blob1_corrupted_index @@ -0,0 +1 @@ +blob1 \ No newline at end of file diff --git a/cmd/desync/testdata/blob1_corrupted_index.caibx b/cmd/desync/testdata/blob1_corrupted_index.caibx new file mode 100644 index 0000000000000000000000000000000000000000..b2387e9a5fda1544d4eefcb5e7e7c5e254eb395e GIT binary patch literal 6504 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 literal 0 HcmV?d00001 diff --git a/fileseed.go b/fileseed.go index 57e5e50..2323436 100644 --- a/fileseed.go +++ b/fileseed.go @@ -1,6 +1,7 @@ package desync import ( + "context" "fmt" "io" "os" @@ -63,12 +64,35 @@ func (s *FileSeed) LongestMatchWith(chunks []IndexChunk) (int, SeedSegment) { return max, newFileSeedSegment(s.srcFile, match, s.canReflink) } +func (s *FileSeed) RegenerateIndex(ctx context.Context, n int) error { + index, _, err := IndexFromFile(ctx, s.srcFile, n, s.index.Index.ChunkSizeMin, s.index.Index.ChunkSizeAvg, + s.index.Index.ChunkSizeMax, nil) + if err != nil { + return err + } + + s.index = index + s.SetInvalid(false) + s.pos = make(map[ChunkID][]int, len(s.index.Chunks)) + for i, c := range s.index.Chunks { + s.pos[c.ID] = append(s.pos[c.ID], i) + } + + return nil +} + func (s *FileSeed) SetInvalid(value bool) { s.mu.Lock() defer s.mu.Unlock() s.isInvalid = value } +func (s *FileSeed) IsInvalid() bool { + s.mu.Lock() + defer s.mu.Unlock() + return s.isInvalid +} + // Returns a slice of chunks from the seed. Compares chunks from position 0 // with seed chunks starting at p. func (s *FileSeed) maxMatchFrom(chunks []IndexChunk, p int) []IndexChunk { diff --git a/nullseed.go b/nullseed.go index ba8dc57..bf48e9f 100644 --- a/nullseed.go +++ b/nullseed.go @@ -1,6 +1,7 @@ package desync import ( + "context" "fmt" "io" "io/ioutil" @@ -64,10 +65,19 @@ func (s *nullChunkSeed) LongestMatchWith(chunks []IndexChunk) (int, SeedSegment) } } +func (s *nullChunkSeed) RegenerateIndex(ctx context.Context, n int) error { + panic("A nullseed can't be regenerated") +} + func (s *nullChunkSeed) SetInvalid(value bool) { panic("A nullseed is never expected to be invalid") } +func (s *nullChunkSeed) IsInvalid() bool { + // A nullseed is never expected to be invalid + return false +} + type nullChunkSection struct { from, to uint64 blockfile *os.File diff --git a/seed.go b/seed.go index dceb0de..a9a73a4 100644 --- a/seed.go +++ b/seed.go @@ -1,6 +1,7 @@ package desync import ( + "context" "os" ) @@ -12,7 +13,9 @@ const DefaultBlockSize = 4096 // existing chunks or blocks into the target from. type Seed interface { LongestMatchWith(chunks []IndexChunk) (int, SeedSegment) + RegenerateIndex(ctx context.Context, n int) error SetInvalid(value bool) + IsInvalid() bool } // SeedSegment represents a matching range between a Seed and a file being diff --git a/selfseed.go b/selfseed.go index 48bf8f7..b8f2413 100644 --- a/selfseed.go +++ b/selfseed.go @@ -1,6 +1,7 @@ package desync import ( + "context" "sync" ) @@ -78,6 +79,15 @@ func (s *selfSeed) getChunk(id ChunkID) SeedSegment { return newFileSeedSegment(s.file, s.index.Chunks[first:first+1], s.canReflink) } +func (s *selfSeed) RegenerateIndex(ctx context.Context, n int) error { + panic("A selfSeed can't be regenerated") +} + func (s *selfSeed) SetInvalid(value bool) { panic("A selfSeed is never expected to be invalid") } + +func (s *selfSeed) IsInvalid() bool { + // A selfSeed is never expected to be invalid + return false +} diff --git a/sequencer.go b/sequencer.go index ebc6d99..0a26f91 100644 --- a/sequencer.go +++ b/sequencer.go @@ -76,6 +76,18 @@ func (r *SeedSequencer) Rewind() { r.current = 0 } +// RegenerateInvalidSeeds regenerates the index to match the unexpected seed content +func (r *SeedSequencer) RegenerateInvalidSeeds(ctx context.Context, n int) error { + for _, s := range r.seeds { + if s.IsInvalid() { + if err := s.RegenerateIndex(ctx, n); err != nil { + return err + } + } + } + return nil +} + // Validate validates a proposed plan by checking if all the chosen chunks // are correctly provided from the seeds. In case a seed has invalid chunks, the // entire seed is marked as invalid and an error is returned.