replication & pruning: ditch replicated-property, use bookmark as cursor instead

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.
This commit is contained in:
Christian Schwarz
2018-09-05 18:24:15 -07:00
parent acd2418803
commit 975fdee217
9 changed files with 559 additions and 294 deletions
+66 -40
View File
@@ -9,13 +9,14 @@ import (
"github.com/zrepl/zrepl/pruning"
"github.com/zrepl/zrepl/replication/pdu"
"net"
"sort"
"sync"
"time"
)
// Try to keep it compatible with gitub.com/zrepl/zrepl/replication.Endpoint
type History interface {
SnapshotReplicationStatus(ctx context.Context, req *pdu.SnapshotReplicationStatusReq) (*pdu.SnapshotReplicationStatusRes, error)
ReplicationCursor(ctx context.Context, req *pdu.ReplicationCursorReq) (*pdu.ReplicationCursorRes, error)
}
type Target interface {
@@ -42,11 +43,12 @@ func GetLogger(ctx context.Context) Logger {
}
type args struct {
ctx context.Context
target Target
receiver History
rules []pruning.KeepRule
retryWait time.Duration
ctx context.Context
target Target
receiver History
rules []pruning.KeepRule
retryWait time.Duration
considerSnapAtCursorReplicated bool
}
type Pruner struct {
@@ -66,9 +68,10 @@ type Pruner struct {
}
type PrunerFactory struct {
senderRules []pruning.KeepRule
receiverRules []pruning.KeepRule
retryWait time.Duration
senderRules []pruning.KeepRule
receiverRules []pruning.KeepRule
retryWait time.Duration
considerSnapAtCursorReplicated bool
}
func checkContainsKeep1(rules []pruning.KeepRule) error {
@@ -95,14 +98,19 @@ func NewPrunerFactory(in config.PruningSenderReceiver) (*PrunerFactory, error) {
return nil, errors.Wrap(err, "cannot build sender pruning rules")
}
if err := checkContainsKeep1(keepRulesSender); err != nil {
return nil, err
considerSnapAtCursorReplicated := false
for _, r := range in.KeepSender {
knr, ok := r.Ret.(*config.PruneKeepNotReplicated)
if !ok {
continue
}
considerSnapAtCursorReplicated = considerSnapAtCursorReplicated || !knr.KeepSnapshotAtCursor
}
f := &PrunerFactory{
keepRulesSender,
keepRulesReceiver,
10 * time.Second, //FIXME constant
considerSnapAtCursorReplicated,
}
return f, nil
}
@@ -115,6 +123,7 @@ func (f *PrunerFactory) BuildSenderPruner(ctx context.Context, target Target, re
receiver,
f.senderRules,
f.retryWait,
f.considerSnapAtCursorReplicated,
},
state: Plan,
}
@@ -129,6 +138,7 @@ func (f *PrunerFactory) BuildReceiverPruner(ctx context.Context, target Target,
receiver,
f.receiverRules,
f.retryWait,
false, // senseless here anyways
},
state: Plan,
}
@@ -254,56 +264,66 @@ func statePlan(a *args, u updater) state {
}
pfss := make([]*fs, len(tfss))
fsloop:
for i, tfs := range tfss {
l := GetLogger(ctx).WithField("fs", tfs.Path)
l.Debug("plan filesystem")
tfsvs, err := target.ListFilesystemVersions(ctx, tfs.Path)
if err != nil {
l.WithError(err).Error("cannot list filesystem versions")
return onErr(u, err)
}
rcReq := &pdu.ReplicationCursorReq{
Filesystem: tfs.Path,
Op: &pdu.ReplicationCursorReq_Get{},
}
rc, err := receiver.ReplicationCursor(ctx, rcReq)
if err != nil {
l.WithError(err).Error("cannot get replication cursor")
return onErr(u, err)
}
if rc.GetError() != "" {
l.WithField("reqErr", rc.GetError()).Error("cannot get replication cursor")
return onErr(u, fmt.Errorf("%s", rc.GetError()))
}
pfs := &fs{
path: tfs.Path,
snaps: make([]pruning.Snapshot, 0, len(tfsvs)),
}
pfss[i] = pfs
// scan from older to newer, all snapshots older than cursor are interpreted as replicated
sort.Slice(tfsvs, func(i, j int) bool {
return tfsvs[i].CreateTXG < tfsvs[j].CreateTXG
})
preCursor := true
for _, tfsv := range tfsvs {
if tfsv.Type != pdu.FilesystemVersion_Snapshot {
continue
}
creation, err := tfsv.CreationAsTime()
if err != nil {
return onErr(u, fmt.Errorf("%s%s has invalid creation date: %s", tfs, tfsv.RelName(), err))
}
req := pdu.SnapshotReplicationStatusReq{
Filesystem: tfs.Path,
Snapshot: tfsv.Name,
Op: pdu.SnapshotReplicationStatusReq_Get,
}
res, err := receiver.SnapshotReplicationStatus(ctx, &req)
if err != nil {
GetLogger(ctx).
WithField("req", req.String()).
WithError(err).Error("cannot get snapshot replication status")
}
if err != nil && shouldRetry(err) {
return onErr(u, err)
} else if err != nil {
pfs.err = err
pfs.snaps = nil
break
}
if res.Status == pdu.SnapshotReplicationStatusRes_Nonexistent {
GetLogger(ctx).
Debug("snapshot does not exist in history, assuming was replicated")
pfs.err = fmt.Errorf("%s%s has invalid creation date: %s", tfs, tfsv.RelName(), err)
l.WithError(pfs.err).Error("")
continue fsloop
}
atCursor := tfsv.Guid == rc.GetGuid()
preCursor = preCursor && !atCursor
pfs.snaps = append(pfs.snaps, snapshot{
replicated: !(res.Status != pdu.SnapshotReplicationStatusRes_Replicated),
replicated: preCursor || (a.considerSnapAtCursorReplicated && atCursor),
date: creation,
fsv: tfsv,
})
}
pfss[i] = pfs
if preCursor {
pfs.err = fmt.Errorf("replication cursor not found in prune target filesystem versions")
l.WithError(pfs.err).Error("")
continue fsloop
}
}
@@ -324,7 +344,13 @@ func stateExec(a *args, u updater) state {
var pfs *fs
state := u(func(pruner *Pruner) {
if len(pruner.prunePending) == 0 {
pruner.state = Done
nextState := Done
for _, pfs := range pruner.pruneCompleted {
if pfs.err != nil {
nextState = ErrPerm
}
}
pruner.state = nextState
return
}
pfs = pruner.prunePending[0]