2019-03-13 16:39:10 +01:00
package diff
2018-05-02 21:26:11 +02:00
import (
2019-03-13 18:43:19 +01:00
"fmt"
2018-05-02 21:26:11 +02:00
"sort"
2019-03-13 18:43:19 +01:00
"strings"
2018-08-16 21:05:21 +02:00
2019-02-22 11:40:27 +01:00
. "github.com/zrepl/zrepl/replication/logic/pdu"
2018-05-02 21:26:11 +02:00
)
type ConflictNoCommonAncestor struct {
2018-06-20 20:20:37 +02:00
SortedSenderVersions , SortedReceiverVersions [ ] * FilesystemVersion
2018-05-02 21:26:11 +02:00
}
func ( c * ConflictNoCommonAncestor ) Error ( ) string {
2019-03-13 18:43:19 +01:00
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 {
2018-06-20 20:20:37 +02:00
SortedSenderVersions , SortedReceiverVersions [ ] * FilesystemVersion
CommonAncestor * FilesystemVersion
SenderOnly , ReceiverOnly [ ] * FilesystemVersion
2018-05-02 21:26:11 +02:00
}
func ( c * ConflictDiverged ) Error ( ) string {
2019-03-13 18:43:19 +01:00
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
}
2022-05-01 14:46:38 +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 ( )
}
2018-06-20 20:20:37 +02:00
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
2018-06-20 20:20:37 +02:00
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
}
2018-06-20 20:20:37 +02:00
sorted := make ( [ ] * FilesystemVersion , len ( fsvslice ) )
2018-05-02 21:26:11 +02:00
copy ( sorted , fsvslice )
sort . Slice ( sorted , lesser ( sorted ) )
return sorted
}
2022-07-18 10:09:50 +02:00
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
}
2018-06-20 20:20:37 +02:00
func IncrementalPath ( receiver , sender [ ] * FilesystemVersion ) ( incPath [ ] * FilesystemVersion , conflict error ) {
2018-05-02 21:26:11 +02:00
2022-07-18 10:09:50 +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 )
2020-08-23 19:21:55 +02:00
var mrcaCandidate struct {
found bool
guid uint64
r , s int
}
2018-09-04 22:32:19 +02:00
2020-08-23 19:21:55 +02:00
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
}
}
2020-08-23 19:21:55 +02:00
// handle failure cases
if ! mrcaCandidate . found {
2022-05-01 14:46:38 +02:00
if len ( sender ) == 0 {
return nil , & ConflictNoSenderSnapshots { }
} else {
return nil , & ConflictNoCommonAncestor {
SortedSenderVersions : sender ,
SortedReceiverVersions : receiver ,
}
2018-05-02 21:26:11 +02:00
}
2020-08-23 19:21:55 +02:00
} else if mrcaCandidate . r != len ( receiver ) - 1 {
2018-05-02 21:26:11 +02:00
return nil , & ConflictDiverged {
SortedSenderVersions : sender ,
SortedReceiverVersions : receiver ,
2020-08-23 19:21:55 +02:00
CommonAncestor : sender [ mrcaCandidate . s ] ,
SenderOnly : sender [ mrcaCandidate . s + 1 : ] ,
ReceiverOnly : receiver [ mrcaCandidate . r + 1 : ] ,
2018-05-02 21:26:11 +02:00
}
}
2020-08-23 19:21:55 +02:00
// incPath is possible
2018-05-02 21:26:11 +02:00
// incPath must not contain bookmarks except initial one,
2018-06-20 20:20:37 +02:00
incPath = make ( [ ] * FilesystemVersion , 0 , len ( sender ) )
2020-08-23 19:21:55 +02:00
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
2020-08-23 19:21:55 +02:00
for i := mrcaCandidate . s + 1 ; i < len ( sender ) ; i ++ {
2018-06-20 20:20:37 +02:00
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
2022-05-01 14:46:38 +02:00
return nil , & ConflictMostRecentSnapshotAlreadyPresent {
SortedSenderVersions : sender ,
SortedReceiverVersions : receiver ,
CommonAncestor : sender [ mrcaCandidate . s ] ,
}
2018-05-02 21:26:11 +02:00
}
return incPath , nil
}