mirror of
https://github.com/zrepl/zrepl.git
synced 2024-12-23 23:48:55 +01:00
975fdee217
A bookmark with a well-known name is used to track which version was
last successfully received by the receiver.
The createtxg that can be retrieved from the bookmark using `zfs get` is
used to set the Replicated attribute of each snap on the sender:
If the snap's CreateTXG > the cursor's, it is not yet replicated,
otherwise it has been.
There is an optional config option to change the behvior to
`CreateTXG >= the cursor's`, and the implementation defaults to that.
The reason: While things work just fine with `CreateTXG > the cursor's`,
ZFS does not provide size estimates in a `zfs send` dry run
(see acd2418
).
However, to enable the use case of keeping the snapshot only around for
the replication, the config flag exists.
115 lines
3.4 KiB
Go
115 lines
3.4 KiB
Go
package mainfsm
|
|
|
|
import (
|
|
"sort"
|
|
|
|
. "github.com/zrepl/zrepl/replication/pdu"
|
|
)
|
|
|
|
type ConflictNoCommonAncestor struct {
|
|
SortedSenderVersions, SortedReceiverVersions []*FilesystemVersion
|
|
}
|
|
|
|
func (c *ConflictNoCommonAncestor) Error() string {
|
|
return "no common snapshot or suitable bookmark between sender and receiver"
|
|
}
|
|
|
|
type ConflictDiverged struct {
|
|
SortedSenderVersions, SortedReceiverVersions []*FilesystemVersion
|
|
CommonAncestor *FilesystemVersion
|
|
SenderOnly, ReceiverOnly []*FilesystemVersion
|
|
}
|
|
|
|
func (c *ConflictDiverged) Error() string {
|
|
return "the receiver's latest snapshot is not present on sender"
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// conflict may be a *ConflictDiverged or a *ConflictNoCommonAncestor
|
|
func IncrementalPath(receiver, sender []*FilesystemVersion) (incPath []*FilesystemVersion, conflict error) {
|
|
|
|
receiver = SortVersionListByCreateTXGThenBookmarkLTSnapshot(receiver)
|
|
sender = SortVersionListByCreateTXGThenBookmarkLTSnapshot(sender)
|
|
|
|
// Find most recent common ancestor by name, preferring snapshots over bookmarks
|
|
|
|
mrcaRcv := len(receiver) - 1
|
|
mrcaSnd := len(sender) - 1
|
|
|
|
for mrcaRcv >= 0 && mrcaSnd >= 0 {
|
|
if receiver[mrcaRcv].Guid == sender[mrcaSnd].Guid {
|
|
// Since we arrive from the end of the array, and because we defined bookmark < snapshot,
|
|
// this condition will match snapshot first, which is what we want because it gives us
|
|
// size estimation
|
|
break
|
|
}
|
|
receiverCreation, err := receiver[mrcaRcv].CreationAsTime()
|
|
if err != nil {
|
|
panic(err) // FIXME move this to a sorting phase before
|
|
}
|
|
senderCreation, err := sender[mrcaSnd].CreationAsTime()
|
|
if err != nil {
|
|
panic(err) // FIXME move this to the sorting phase before
|
|
}
|
|
|
|
if receiverCreation.Before(senderCreation) {
|
|
mrcaSnd--
|
|
} else {
|
|
mrcaRcv--
|
|
}
|
|
}
|
|
|
|
if mrcaRcv == -1 || mrcaSnd == -1 {
|
|
return nil, &ConflictNoCommonAncestor{
|
|
SortedSenderVersions: sender,
|
|
SortedReceiverVersions: receiver,
|
|
}
|
|
}
|
|
|
|
if mrcaRcv != len(receiver)-1 {
|
|
return nil, &ConflictDiverged{
|
|
SortedSenderVersions: sender,
|
|
SortedReceiverVersions: receiver,
|
|
CommonAncestor: sender[mrcaSnd],
|
|
SenderOnly: sender[mrcaSnd+1:],
|
|
ReceiverOnly: receiver[mrcaRcv+1:],
|
|
}
|
|
}
|
|
|
|
// incPath must not contain bookmarks except initial one,
|
|
incPath = make([]*FilesystemVersion, 0, len(sender))
|
|
incPath = append(incPath, sender[mrcaSnd])
|
|
// it's ok if incPath[0] is a bookmark, but not the subsequent ones in the incPath
|
|
for i := mrcaSnd + 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
|
|
incPath = incPath[1:]
|
|
}
|
|
return incPath, nil
|
|
}
|