handle changes to placeholder state correctly

We assumed that `zfs recv -F FS` would basically replace FS inplace, leaving its children untouched.
That is in fact not the case, it only works if `zfs send -R` is set, which we don't do.

Thus, implement the required functionality manually.

This solves a `zfs recv` error that would occur when a filesystem previously created as placeholder on the receiving side becomes a non-placeholder filesystem (likely due to config change on the sending side):

  zfs send pool1/foo@1 | zfs recv -F pool1/bar
  cannot receive new filesystem stream:
  destination has snapshots (eg. pool1/bar)
  must destroy them to overwrite it
This commit is contained in:
Christian Schwarz
2019-03-13 18:33:20 +01:00
parent 1eb0f12a61
commit d50e553ebb
6 changed files with 196 additions and 116 deletions

View File

@ -48,9 +48,10 @@ func (s *Sender) ListFilesystems(ctx context.Context, r *pdu.ListFilesystemReq)
rfss[i] = &pdu.Filesystem{
Path: fss[i].ToString(),
// FIXME: not supporting ResumeToken yet
IsPlaceholder: false, // sender FSs are never placeholders
}
}
res := &pdu.ListFilesystemRes{Filesystems: rfss, Empty: len(rfss) == 0}
res := &pdu.ListFilesystemRes{Filesystems: rfss}
return res, nil
}
@ -118,7 +119,7 @@ func (p *Sender) PingDataconn(ctx context.Context, req *pdu.PingReq) (*pdu.PingR
return p.Ping(ctx, req)
}
func (p *Sender) WaitForConnectivity(ctx context.Context) (error) {
func (p *Sender) WaitForConnectivity(ctx context.Context) error {
return nil
}
@ -243,7 +244,7 @@ func (s *Receiver) ListFilesystems(ctx context.Context, req *pdu.ListFilesystemR
if err != nil {
return nil, err
}
// present without prefix, and only those that are not placeholders
// present filesystem without the root_fs prefix
fss := make([]*pdu.Filesystem, 0, len(filtered))
for _, a := range filtered {
ph, err := zfs.ZFSIsPlaceholderFilesystem(a)
@ -254,21 +255,16 @@ func (s *Receiver) ListFilesystems(ctx context.Context, req *pdu.ListFilesystemR
Error("inconsistent placeholder property")
return nil, errors.New("server error: inconsistent placeholder property") // don't leak path
}
if ph {
getLogger(ctx).
WithField("fs", a.ToString()).
Debug("ignoring placeholder filesystem")
continue
}
getLogger(ctx).
WithField("fs", a.ToString()).
Debug("non-placeholder filesystem")
WithField("is_placeholder", ph).
Debug("filesystem")
a.TrimPrefix(root)
fss = append(fss, &pdu.Filesystem{Path: a.ToString()})
fss = append(fss, &pdu.Filesystem{Path: a.ToString(), IsPlaceholder: ph})
}
if len(fss) == 0 {
getLogger(ctx).Debug("no non-placeholder filesystems")
return &pdu.ListFilesystemRes{Empty: true}, nil
getLogger(ctx).Debug("no filesystems found")
return &pdu.ListFilesystemRes{}, nil
}
return &pdu.ListFilesystemRes{Filesystems: fss}, nil
}
@ -304,7 +300,7 @@ func (s *Receiver) PingDataconn(ctx context.Context, req *pdu.PingReq) (*pdu.Pin
return s.Ping(ctx, req)
}
func (s *Receiver) WaitForConnectivity(ctx context.Context) (error) {
func (s *Receiver) WaitForConnectivity(ctx context.Context) error {
return nil
}
@ -316,7 +312,6 @@ func (s *Receiver) Send(ctx context.Context, req *pdu.SendReq) (*pdu.SendRes, zf
return nil, nil, fmt.Errorf("receiver does not implement Send()")
}
func (s *Receiver) Receive(ctx context.Context, req *pdu.ReceiveReq, receive zfs.StreamCopier) (*pdu.ReceiveRes, error) {
getLogger(ctx).Debug("incoming Receive")
defer receive.Close()
@ -357,25 +352,27 @@ func (s *Receiver) Receive(ctx context.Context, req *pdu.ReceiveReq, receive zfs
return nil, err
}
needForceRecv := false
var clearPlaceholderProperty bool
var recvOpts zfs.RecvOptions
props, err := zfs.ZFSGet(lp, []string{zfs.ZREPL_PLACEHOLDER_PROPERTY_NAME})
if err == nil {
if isPlaceholder, _ := zfs.IsPlaceholder(lp, props.Get(zfs.ZREPL_PLACEHOLDER_PROPERTY_NAME)); isPlaceholder {
needForceRecv = true
recvOpts.RollbackAndForceRecv = true
clearPlaceholderProperty = true
}
}
if clearPlaceholderProperty {
if err := zfs.ZFSSetNoPlaceholder(lp); err != nil {
return nil, fmt.Errorf("cannot clear placeholder property for forced receive: %s", err)
}
}
args := make([]string, 0, 1)
if needForceRecv {
args = append(args, "-F")
}
getLogger(ctx).WithField("opts", fmt.Sprintf("%#v", recvOpts)).Debug("start receive command")
getLogger(ctx).Debug("start receive command")
if err := zfs.ZFSRecv(ctx, lp.ToString(), receive, args...); err != nil {
if err := zfs.ZFSRecv(ctx, lp.ToString(), receive, recvOpts); err != nil {
getLogger(ctx).
WithError(err).
WithField("args", args).
WithField("opts", recvOpts).
Error("zfs receive failed")
return nil, err
}