mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-29 04:07:17 +00:00
* filer.backup: key the checkpoint by source path and sink destination The checkpoint id hashed only sink name + directory, so two backups to different buckets or endpoints sharing a directory layout advanced one checkpoint: whichever job was running pushed the shared offset forward, and a stopped or failing job later resumed from the other's position, silently skipping changes. Backups of different source paths to the same destination shared a checkpoint the same way. Each sink now reports a destination identity (endpoint or account, bucket or container, directory) and the checkpoint is keyed by the source path plus that identity. Reads fall back to the historical name+directory key when the new key has no value, so existing backups resume where they left off; writes go only to the new key. * filer.sync: include the target path in the offset key The offset stored on the target filer was keyed by source path and source filer signature only, so two syncs from the same source cluster and path to different directories on the same target cluster advanced one shared checkpoint, and the slower one could resume past events it never applied. The target path now participates in the key; "/" keeps the historical form, and a sync with a non-root target path falls back to the historical key once when its own key has no value yet. * join checkpoint key fields with NUL so they cannot alias A path or configuration value spelling out the separator could concatenate two different field tuples to the same checkpoint key. NUL cannot appear in a CLI path argument or any sane configuration value, making the encoding injective.
77 lines
3.3 KiB
Go
77 lines
3.3 KiB
Go
package command
|
|
|
|
import "testing"
|
|
|
|
// The offset key is a persistence contract: a changed form orphans saved
|
|
// checkpoints, so the historical forms must survive verbatim and the
|
|
// target-scoped form must stay stable.
|
|
func TestGetSignaturePrefixByPath_KeyForms(t *testing.T) {
|
|
cases := []struct {
|
|
sourcePath, targetPath, want string
|
|
}{
|
|
// historical forms, kept so existing deployments resume unchanged
|
|
{"/", "/", "sync."},
|
|
{"/data", "/", "sync./data"},
|
|
// the target path participates once it deviates from "/"
|
|
{"/", "/backup-a", "sync.\x00/backup-a"},
|
|
{"/data", "/backup-a", "sync./data\x00/backup-a"},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := getSignaturePrefixByPath(tc.sourcePath, tc.targetPath); got != tc.want {
|
|
t.Errorf("getSignaturePrefixByPath(%q, %q) = %q, want %q", tc.sourcePath, tc.targetPath, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The NUL separator keeps the key injective: a source directory whose name
|
|
// spells out a separator must not alias a different (source, target) pair.
|
|
func TestGetSignaturePrefixByPath_NoAliasing(t *testing.T) {
|
|
if a, b := getSignaturePrefixByPath("/archive=>/backup", "/"), getSignaturePrefixByPath("/archive", "/backup"); a == b {
|
|
t.Errorf("source path spelling a separator aliases key %q", a)
|
|
}
|
|
}
|
|
|
|
// Two syncs from the same source filer and path to different directories on
|
|
// the same target filer must keep separate checkpoints, or the running one
|
|
// pushes the shared offset past events the other never processed.
|
|
func TestGetSignaturePrefixByPath_DistinctTargets(t *testing.T) {
|
|
a := getSignaturePrefixByPath("/", "/backup-a")
|
|
b := getSignaturePrefixByPath("/", "/backup-b")
|
|
if a == b {
|
|
t.Errorf("different target paths share offset key %q", a)
|
|
}
|
|
if root := getSignaturePrefixByPath("/", "/"); a == root {
|
|
t.Errorf("non-root target path shares offset key %q with the root form", a)
|
|
}
|
|
}
|
|
|
|
// Over the real KV wire path: a sync that predates target-scoped keys resumes
|
|
// from the historical key, and once each sync checkpoints under its own key
|
|
// they no longer disturb each other.
|
|
func TestSyncOffset_HistoricalFallbackThenIndependence(t *testing.T) {
|
|
filerAddr, dial := startKvFiler(t)
|
|
const sourceSig = int32(12345)
|
|
historical := getSignaturePrefixByPath("/data", "/")
|
|
prefixA := getSignaturePrefixByPath("/data", "/backup-a")
|
|
prefixB := getSignaturePrefixByPath("/data", "/backup-b")
|
|
|
|
// pre-upgrade state: both syncs shared the historical key
|
|
if err := setOffset(dial, filerAddr, historical, sourceSig, 111); err != nil {
|
|
t.Fatalf("seed historical offset: %v", err)
|
|
}
|
|
if got, err := getOffsetWithFallback(dial, filerAddr, prefixA, sourceSig, historical, sourceSig); err != nil || got != 111 {
|
|
t.Fatalf("A fallback read = (%d, %v), want (111, nil)", got, err)
|
|
}
|
|
|
|
// A checkpoints under its own key; B still resumes from the historical one
|
|
if err := setOffset(dial, filerAddr, prefixA, sourceSig, 222); err != nil {
|
|
t.Fatalf("write A offset: %v", err)
|
|
}
|
|
if got, err := getOffsetWithFallback(dial, filerAddr, prefixA, sourceSig, historical, sourceSig); err != nil || got != 222 {
|
|
t.Fatalf("A read = (%d, %v), want (222, nil)", got, err)
|
|
}
|
|
if got, err := getOffsetWithFallback(dial, filerAddr, prefixB, sourceSig, historical, sourceSig); err != nil || got != 111 {
|
|
t.Fatalf("B read = (%d, %v), want (111, nil)", got, err)
|
|
}
|
|
}
|