zrepl/replication/logic/diff/diff_test.go
Christian Schwarz 2642c64303 make initial replication policy configurable (most_recent, all, fail)
Config:

```
- type: push
  ...
  conflict_resolution:
    initial_replication: most_recent | all | fali
```

The ``initial_replication`` option determines which snapshots zrepl
replicates if the filesystem has not been replicated before.
If ``most_recent`` (the default), the initial replication will only
transfer the most recent snapshot, while ignoring previous snapshots.
If all snapshots should be replicated, specify ``all``.
Use ``fail`` to make replication of the filesystem fail in case
there is no corresponding fileystem on the receiver.

Code-Level Changes, apart from the obvious:
- Rework IncrementalPath()'s return signature.
  Now returns an error for initial replications as well.
- Rename & rework it's consumer, resolveConflict().

Co-authored-by: Graham Christensen <graham@grahamc.com>

Fixes https://github.com/zrepl/zrepl/issues/550
Fixes https://github.com/zrepl/zrepl/issues/187
Closes https://github.com/zrepl/zrepl/pull/592
2022-06-26 14:36:59 +02:00

156 lines
4.6 KiB
Go

package diff
import (
"strconv"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
. "github.com/zrepl/zrepl/replication/logic/pdu"
)
func fsvlist(fsv ...string) (r []*FilesystemVersion) {
r = make([]*FilesystemVersion, len(fsv))
for i, f := range fsv {
// parse the id from fsvlist. it is used to derive Guid,CreateTXG and Creation attrs
split := strings.Split(f, ",")
if len(split) != 2 {
panic("invalid fsv spec")
}
id, err := strconv.Atoi(split[1])
if err != nil {
panic(err)
}
creation := func(id int) string {
return FilesystemVersionCreation(time.Unix(0, 0).Add(time.Duration(id) * time.Second))
}
if strings.HasPrefix(f, "#") {
r[i] = &FilesystemVersion{
Name: strings.TrimPrefix(f, "#"),
Type: FilesystemVersion_Bookmark,
Guid: uint64(id),
CreateTXG: uint64(id),
Creation: creation(id),
}
} else if strings.HasPrefix(f, "@") {
r[i] = &FilesystemVersion{
Name: strings.TrimPrefix(f, "@"),
Type: FilesystemVersion_Snapshot,
Guid: uint64(id),
CreateTXG: uint64(id),
Creation: creation(id),
}
} else {
panic("invalid character")
}
}
return
}
func doTest(receiver, sender []*FilesystemVersion, validate func(incpath []*FilesystemVersion, conflict error)) {
p, err := IncrementalPath(receiver, sender)
validate(p, err)
}
func TestIncrementalPath_SnapshotsOnly(t *testing.T) {
l := fsvlist
// basic functionality
doTest(l("@a,1", "@b,2"), l("@a,1", "@b,2", "@c,3", "@d,4"), func(path []*FilesystemVersion, conflict error) {
assert.Equal(t, l("@b,2", "@c,3", "@d,4"), path)
})
// no common ancestor
doTest(l(), l("@a,1"), func(path []*FilesystemVersion, conflict error) {
assert.Nil(t, path)
ca, ok := conflict.(*ConflictNoCommonAncestor)
require.True(t, ok)
assert.Equal(t, l("@a,1"), ca.SortedSenderVersions)
})
doTest(l("@a,1", "@b,2"), l("@c,3", "@d,4"), func(path []*FilesystemVersion, conflict error) {
assert.Nil(t, path)
ca, ok := conflict.(*ConflictNoCommonAncestor)
require.True(t, ok)
assert.Equal(t, l("@a,1", "@b,2"), ca.SortedReceiverVersions)
assert.Equal(t, l("@c,3", "@d,4"), ca.SortedSenderVersions)
})
// divergence is detected
doTest(l("@a,1", "@b1,2"), l("@a,1", "@b2,3"), func(path []*FilesystemVersion, conflict error) {
assert.Nil(t, path)
cd, ok := conflict.(*ConflictDiverged)
require.True(t, ok)
assert.Equal(t, l("@a,1")[0], cd.CommonAncestor)
assert.Equal(t, l("@b1,2"), cd.ReceiverOnly)
assert.Equal(t, l("@b2,3"), cd.SenderOnly)
})
// gaps before most recent common ancestor do not matter
doTest(l("@a,1", "@b,2", "@c,3"), l("@a,1", "@c,3", "@d,4"), func(path []*FilesystemVersion, conflict error) {
assert.Equal(t, l("@c,3", "@d,4"), path)
})
// nothing to do if fully shared history
doTest(l("@a,1", "@b,2"), l("@a,1", "@b,2"), func(incpath []*FilesystemVersion, conflict error) {
assert.Nil(t, incpath)
assert.NotNil(t, conflict)
_, ok := conflict.(*ConflictMostRecentSnapshotAlreadyPresent)
assert.True(t, ok)
})
// ...but it's sufficient if the most recent snapshot is present
doTest(l("@c,3"), l("@a,1", "@b,2", "@c,3"), func(path []*FilesystemVersion, conflict error) {
assert.Nil(t, path)
_, ok := conflict.(*ConflictMostRecentSnapshotAlreadyPresent)
assert.True(t, ok)
})
// no sender snapshots errors: empty receiver
doTest(l(), l(), func(incpath []*FilesystemVersion, conflict error) {
assert.Nil(t, incpath)
assert.NotNil(t, conflict)
t.Logf("%T", conflict)
_, ok := conflict.(*ConflictNoSenderSnapshots)
assert.True(t, ok)
})
// no sender snapshots errors: snapshots on receiver
doTest(l("@a,1"), l(), func(incpath []*FilesystemVersion, conflict error) {
assert.Nil(t, incpath)
assert.NotNil(t, conflict)
t.Logf("%T", conflict)
_, ok := conflict.(*ConflictNoSenderSnapshots)
assert.True(t, ok)
})
}
func TestIncrementalPath_BookmarkSupport(t *testing.T) {
l := fsvlist
// bookmarks are used
doTest(l("@a,1"), l("#a,1", "@b,2"), func(path []*FilesystemVersion, conflict error) {
assert.Equal(t, l("#a,1", "@b,2"), path)
})
// bookmarks are stripped from IncrementalPath (cannot send incrementally)
doTest(l("@a,1"), l("#a,1", "#b,2", "@c,3"), func(path []*FilesystemVersion, conflict error) {
assert.Equal(t, l("#a,1", "@c,3"), path)
})
// test that snapshots are preferred over bookmarks in IncrementalPath
doTest(l("@a,1"), l("#a,1", "@a,1", "@b,2"), func(path []*FilesystemVersion, conflict error) {
assert.Equal(t, l("@a,1", "@b,2"), path)
})
doTest(l("@a,1"), l("@a,1", "#a,1", "@b,2"), func(path []*FilesystemVersion, conflict error) {
assert.Equal(t, l("@a,1", "@b,2"), path)
})
}