zrepl/replication/logic/diff/diff.go

175 lines
5.4 KiB
Go
Raw Normal View History

package diff
2018-05-02 21:26:11 +02:00
import (
"fmt"
2018-05-02 21:26:11 +02:00
"sort"
"strings"
. "github.com/zrepl/zrepl/replication/logic/pdu"
2018-05-02 21:26:11 +02:00
)
type ConflictNoCommonAncestor struct {
SortedSenderVersions, SortedReceiverVersions []*FilesystemVersion
2018-05-02 21:26:11 +02:00
}
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()
2018-05-02 21:26:11 +02:00
}
type ConflictDiverged struct {
SortedSenderVersions, SortedReceiverVersions []*FilesystemVersion
CommonAncestor *FilesystemVersion
SenderOnly, ReceiverOnly []*FilesystemVersion
2018-05-02 21:26:11 +02:00
}
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()
2018-05-02 21:26:11 +02:00
}
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 {
2018-05-02 21:26:11 +02:00
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
2018-05-02 21:26:11 +02:00
}
return false
}
}
if sort.SliceIsSorted(fsvslice, lesser(fsvslice)) {
return fsvslice
}
sorted := make([]*FilesystemVersion, len(fsvslice))
2018-05-02 21:26:11 +02:00
copy(sorted, fsvslice)
sort.Slice(sorted, lesser(sorted))
return sorted
}
func StripBookmarksFromVersionList(fsvslice []*FilesystemVersion) []*FilesystemVersion {
fslice := make([]*FilesystemVersion, 0, len(fsvslice))
for _, fv := range fsvslice {
if fv.Type != FilesystemVersion_Bookmark {
fslice = append(fslice, fv)
}
}
return fslice
}
func IncrementalPath(receiver, sender []*FilesystemVersion) (incPath []*FilesystemVersion, conflict error) {
2018-05-02 21:26:11 +02:00
// Receive-side bookmarks can't be used as incremental-from,
// and don't cause recv to fail if there is a newer bookmark than incremetal-form on the receiver.
// So, simply mask them out.
// This will also hide them in the report, but it keeps the code in this function simple,
// and a user who complains about them missing in a conflict message will likely require
// more education about bookmarks than a slightly more accurate error message. They'll get
// that when they open an issue.
receiver = StripBookmarksFromVersionList(receiver)
2018-05-02 21:26:11 +02:00
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
}
2018-05-02 21:26:11 +02:00
}
}
// handle failure cases
if !mrcaCandidate.found {
if len(sender) == 0 {
return nil, &ConflictNoSenderSnapshots{}
} else {
return nil, &ConflictNoCommonAncestor{
SortedSenderVersions: sender,
SortedReceiverVersions: receiver,
}
2018-05-02 21:26:11 +02:00
}
} else if mrcaCandidate.r != len(receiver)-1 {
2018-05-02 21:26:11 +02:00
return nil, &ConflictDiverged{
SortedSenderVersions: sender,
SortedReceiverVersions: receiver,
CommonAncestor: sender[mrcaCandidate.s],
SenderOnly: sender[mrcaCandidate.s+1:],
ReceiverOnly: receiver[mrcaCandidate.r+1:],
2018-05-02 21:26:11 +02:00
}
}
// incPath is possible
2018-05-02 21:26:11 +02:00
// incPath must not contain bookmarks except initial one,
incPath = make([]*FilesystemVersion, 0, len(sender))
incPath = append(incPath, sender[mrcaCandidate.s])
2018-05-02 21:26:11 +02:00
// 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 {
2018-05-02 21:26:11 +02:00
incPath = append(incPath, sender[i])
}
}
if len(incPath) == 1 {
// nothing to do
return nil, &ConflictMostRecentSnapshotAlreadyPresent{
SortedSenderVersions: sender,
SortedReceiverVersions: receiver,
CommonAncestor: sender[mrcaCandidate.s],
}
2018-05-02 21:26:11 +02:00
}
return incPath, nil
}