mirror of
https://github.com/zrepl/zrepl.git
synced 2024-12-12 18:22:22 +01:00
2642c64303
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
156 lines
4.6 KiB
Go
156 lines
4.6 KiB
Go
package diff
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
. "github.com/zrepl/zrepl/replication/logic/pdu"
|
|
)
|
|
|
|
type ConflictNoCommonAncestor struct {
|
|
SortedSenderVersions, SortedReceiverVersions []*FilesystemVersion
|
|
}
|
|
|
|
func (c *ConflictNoCommonAncestor) Error() string {
|
|
var buf strings.Builder
|
|
buf.WriteString("no common snapshot or suitable bookmark between sender and receiver")
|
|
if len(c.SortedReceiverVersions) > 0 || len(c.SortedSenderVersions) > 0 {
|
|
buf.WriteString(":\n sorted sender versions:\n")
|
|
for _, v := range c.SortedSenderVersions {
|
|
fmt.Fprintf(&buf, " %s\n", v.RelName())
|
|
}
|
|
buf.WriteString(" sorted receiver versions:\n")
|
|
for _, v := range c.SortedReceiverVersions {
|
|
fmt.Fprintf(&buf, " %s\n", v.RelName())
|
|
}
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
type ConflictDiverged struct {
|
|
SortedSenderVersions, SortedReceiverVersions []*FilesystemVersion
|
|
CommonAncestor *FilesystemVersion
|
|
SenderOnly, ReceiverOnly []*FilesystemVersion
|
|
}
|
|
|
|
func (c *ConflictDiverged) Error() string {
|
|
var buf strings.Builder
|
|
buf.WriteString("the receiver's latest snapshot is not present on sender:\n")
|
|
fmt.Fprintf(&buf, " last common: %s\n", c.CommonAncestor.RelName())
|
|
fmt.Fprintf(&buf, " sender-only:\n")
|
|
for _, v := range c.SenderOnly {
|
|
fmt.Fprintf(&buf, " %s\n", v.RelName())
|
|
}
|
|
fmt.Fprintf(&buf, " receiver-only:\n")
|
|
for _, v := range c.ReceiverOnly {
|
|
fmt.Fprintf(&buf, " %s\n", v.RelName())
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
type ConflictNoSenderSnapshots struct{}
|
|
|
|
func (c *ConflictNoSenderSnapshots) Error() string {
|
|
return "no snapshots available on sender side"
|
|
}
|
|
|
|
type ConflictMostRecentSnapshotAlreadyPresent struct {
|
|
SortedSenderVersions, SortedReceiverVersions []*FilesystemVersion
|
|
CommonAncestor *FilesystemVersion
|
|
}
|
|
|
|
func (c *ConflictMostRecentSnapshotAlreadyPresent) Error() string {
|
|
var buf strings.Builder
|
|
fmt.Fprintf(&buf, "the most recent sender snapshot is already present on the receiver (guid=%v, name=%q)", c.CommonAncestor.GetGuid(), c.CommonAncestor.RelName())
|
|
return buf.String()
|
|
}
|
|
|
|
func SortVersionListByCreateTXGThenBookmarkLTSnapshot(fsvslice []*FilesystemVersion) []*FilesystemVersion {
|
|
lesser := func(s []*FilesystemVersion) func(i, j int) bool {
|
|
return func(i, j int) bool {
|
|
if s[i].CreateTXG < s[j].CreateTXG {
|
|
return true
|
|
}
|
|
if s[i].CreateTXG == s[j].CreateTXG {
|
|
// Bookmark < Snapshot
|
|
return s[i].Type == FilesystemVersion_Bookmark && s[j].Type == FilesystemVersion_Snapshot
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
if sort.SliceIsSorted(fsvslice, lesser(fsvslice)) {
|
|
return fsvslice
|
|
}
|
|
sorted := make([]*FilesystemVersion, len(fsvslice))
|
|
copy(sorted, fsvslice)
|
|
sort.Slice(sorted, lesser(sorted))
|
|
return sorted
|
|
}
|
|
|
|
func IncrementalPath(receiver, sender []*FilesystemVersion) (incPath []*FilesystemVersion, conflict error) {
|
|
|
|
receiver = SortVersionListByCreateTXGThenBookmarkLTSnapshot(receiver)
|
|
sender = SortVersionListByCreateTXGThenBookmarkLTSnapshot(sender)
|
|
|
|
var mrcaCandidate struct {
|
|
found bool
|
|
guid uint64
|
|
r, s int
|
|
}
|
|
|
|
findCandidate:
|
|
for r := len(receiver) - 1; r >= 0; r-- {
|
|
for s := len(sender) - 1; s >= 0; s-- {
|
|
if sender[s].GetGuid() == receiver[r].GetGuid() {
|
|
mrcaCandidate.guid = sender[s].GetGuid()
|
|
mrcaCandidate.s = s
|
|
mrcaCandidate.r = r
|
|
mrcaCandidate.found = true
|
|
break findCandidate
|
|
}
|
|
}
|
|
}
|
|
|
|
// handle failure cases
|
|
if !mrcaCandidate.found {
|
|
if len(sender) == 0 {
|
|
return nil, &ConflictNoSenderSnapshots{}
|
|
} else {
|
|
return nil, &ConflictNoCommonAncestor{
|
|
SortedSenderVersions: sender,
|
|
SortedReceiverVersions: receiver,
|
|
}
|
|
}
|
|
} else if mrcaCandidate.r != len(receiver)-1 {
|
|
return nil, &ConflictDiverged{
|
|
SortedSenderVersions: sender,
|
|
SortedReceiverVersions: receiver,
|
|
CommonAncestor: sender[mrcaCandidate.s],
|
|
SenderOnly: sender[mrcaCandidate.s+1:],
|
|
ReceiverOnly: receiver[mrcaCandidate.r+1:],
|
|
}
|
|
}
|
|
|
|
// incPath is possible
|
|
|
|
// incPath must not contain bookmarks except initial one,
|
|
incPath = make([]*FilesystemVersion, 0, len(sender))
|
|
incPath = append(incPath, sender[mrcaCandidate.s])
|
|
// it's ok if incPath[0] is a bookmark, but not the subsequent ones in the incPath
|
|
for i := mrcaCandidate.s + 1; i < len(sender); i++ {
|
|
if sender[i].Type == FilesystemVersion_Snapshot && incPath[len(incPath)-1].Guid != sender[i].Guid {
|
|
incPath = append(incPath, sender[i])
|
|
}
|
|
}
|
|
if len(incPath) == 1 {
|
|
// nothing to do
|
|
return nil, &ConflictMostRecentSnapshotAlreadyPresent{
|
|
SortedSenderVersions: sender,
|
|
SortedReceiverVersions: receiver,
|
|
CommonAncestor: sender[mrcaCandidate.s],
|
|
}
|
|
}
|
|
return incPath, nil
|
|
}
|