Bind the local index to its backup destination
The local index tracks which chunks and blobs already exist at the backup destination. Nothing was recording *which* destination, so changing storage_url and running a backup left the scanner treating every already-seen chunk as still-present at the new (empty) location. Uploads were skipped silently and the resulting snapshots pointed at blobs that don't exist at the new destination. Fix: record storage_url in a new local_meta key-value table on first mutating command, and refuse to proceed when the configured URL later differs from the stored one. The error explains the two recovery paths (revert the config, or run 'vaultik database purge' to discard the index and rebuild from a fresh full backup). Wired into snapshot create / prune / snapshot remove / snapshot purge / snapshot cleanup. Read-only inspection commands (snapshot list, remote info, store info) are exempt.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package vaultik_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sneak.berlin/go/vaultik/internal/config"
|
||||
"sneak.berlin/go/vaultik/internal/database"
|
||||
"sneak.berlin/go/vaultik/internal/log"
|
||||
"sneak.berlin/go/vaultik/internal/vaultik"
|
||||
)
|
||||
|
||||
// buildBindTestVaultik returns a minimal Vaultik wired with a real
|
||||
// in-memory DB and a config carrying the given StorageURL. Enough to
|
||||
// exercise EnsureStorageBinding without spinning up storage or fx.
|
||||
func buildBindTestVaultik(t *testing.T, storageURL string) (*vaultik.TestVaultik, *database.Repositories) {
|
||||
t.Helper()
|
||||
|
||||
db, err := database.NewTestDB()
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = db.Close() })
|
||||
|
||||
repos := database.NewRepositories(db)
|
||||
|
||||
tv := vaultik.NewForTesting(nil)
|
||||
tv.Repositories = repos
|
||||
tv.DB = db
|
||||
tv.Config = &config.Config{StorageURL: storageURL}
|
||||
tv.SetContext(context.Background())
|
||||
|
||||
return tv, repos
|
||||
}
|
||||
|
||||
func TestEnsureStorageBinding_FreshDBRecordsURL(t *testing.T) {
|
||||
log.Initialize(log.Config{})
|
||||
|
||||
tv, repos := buildBindTestVaultik(t, "file:///mnt/backups/new")
|
||||
|
||||
require.NoError(t, tv.EnsureStorageBinding())
|
||||
|
||||
got, err := repos.LocalMeta.Get(context.Background(), database.LocalMetaKeyStorageURL)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "file:///mnt/backups/new", got, "first call must record the configured URL")
|
||||
}
|
||||
|
||||
func TestEnsureStorageBinding_MatchingURLPasses(t *testing.T) {
|
||||
log.Initialize(log.Config{})
|
||||
|
||||
tv, repos := buildBindTestVaultik(t, "s3://bucket/prefix")
|
||||
|
||||
require.NoError(t, repos.LocalMeta.Set(context.Background(),
|
||||
database.LocalMetaKeyStorageURL, "s3://bucket/prefix"))
|
||||
|
||||
require.NoError(t, tv.EnsureStorageBinding())
|
||||
}
|
||||
|
||||
func TestEnsureStorageBinding_MismatchRefuses(t *testing.T) {
|
||||
log.Initialize(log.Config{})
|
||||
|
||||
tv, repos := buildBindTestVaultik(t, "file:///mnt/backups/new")
|
||||
|
||||
require.NoError(t, repos.LocalMeta.Set(context.Background(),
|
||||
database.LocalMetaKeyStorageURL, "file:///mnt/backups/old"))
|
||||
|
||||
err := tv.EnsureStorageBinding()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "file:///mnt/backups/old")
|
||||
assert.Contains(t, err.Error(), "file:///mnt/backups/new")
|
||||
assert.Contains(t, err.Error(), "vaultik database purge")
|
||||
}
|
||||
Reference in New Issue
Block a user