[#342] endpoint: always create unencrypted placeholder filesystems

This "breaks" the use case of receiving an unencrypted send into an encrypted receiver by setting the receiver's `root_fs`'s `encryption=on`.
"breaks" in air-quotes because we have not yet released a version of
zrepl with encrypted send support.

We will bring back the featured outlined above in a future release.
See https://github.com/zrepl/zrepl/issues/342#issuecomment-657231818 and following.
This commit is contained in:
Christian Schwarz 2020-07-12 16:32:28 +02:00
parent 4b8f0ad112
commit 8ff83f2f1a
4 changed files with 58 additions and 21 deletions

View File

@ -679,7 +679,7 @@ func (s *Receiver) Receive(ctx context.Context, req *pdu.ReceiveReq, receive io.
f := zfs.NewDatasetPathForest() f := zfs.NewDatasetPathForest()
f.Add(lp) f.Add(lp)
getLogger(ctx).Debug("begin tree-walk") getLogger(ctx).Debug("begin tree-walk")
f.WalkTopDown(func(v zfs.DatasetPathVisit) (visitChildTree bool) { f.WalkTopDown(func(v *zfs.DatasetPathVisit) (visitChildTree bool) {
if v.Path.Equal(lp) { if v.Path.Equal(lp) {
return false return false
} }
@ -707,7 +707,7 @@ func (s *Receiver) Receive(ctx context.Context, req *pdu.ReceiveReq, receive io.
} }
l := getLogger(ctx).WithField("placeholder_fs", v.Path) l := getLogger(ctx).WithField("placeholder_fs", v.Path)
l.Debug("create placeholder filesystem") l.Debug("create placeholder filesystem")
err := zfs.ZFSCreatePlaceholderFilesystem(ctx, v.Path) err := zfs.ZFSCreatePlaceholderFilesystem(ctx, v.Path, v.Parent.Path)
if err != nil { if err != nil {
l.WithError(err).Error("cannot create placeholder filesystem") l.WithError(err).Error("cannot create placeholder filesystem")
visitErr = err visitErr = err

View File

@ -33,9 +33,10 @@ type DatasetPathVisit struct {
Path *DatasetPath Path *DatasetPath
// If true, the dataset referenced by Path was not in the list of datasets to traverse // If true, the dataset referenced by Path was not in the list of datasets to traverse
FilledIn bool FilledIn bool
Parent *DatasetPathVisit
} }
type DatasetPathsVisitor func(v DatasetPathVisit) (visitChildTree bool) type DatasetPathsVisitor func(v *DatasetPathVisit) (visitChildTree bool)
// Traverse a list of DatasetPaths top down, i.e. given a set of datasets with same // Traverse a list of DatasetPaths top down, i.e. given a set of datasets with same
// path prefix, those with shorter prefix are traversed first. // path prefix, those with shorter prefix are traversed first.
@ -44,7 +45,11 @@ type DatasetPathsVisitor func(v DatasetPathVisit) (visitChildTree bool)
func (f *DatasetPathForest) WalkTopDown(visitor DatasetPathsVisitor) { func (f *DatasetPathForest) WalkTopDown(visitor DatasetPathsVisitor) {
for _, r := range f.roots { for _, r := range f.roots {
r.WalkTopDown([]string{}, visitor) r.WalkTopDown(&DatasetPathVisit{
Path: &DatasetPath{nil},
FilledIn: true,
Parent: nil,
}, visitor)
} }
} }
@ -88,19 +93,21 @@ func (t *datasetPathTree) Add(p []string) bool {
} }
func (t *datasetPathTree) WalkTopDown(parent []string, visitor DatasetPathsVisitor) { func (t *datasetPathTree) WalkTopDown(parent *DatasetPathVisit, visitor DatasetPathsVisitor) {
this := append(parent, t.Component) thisVisitPath := parent.Path.Copy()
thisVisitPath.Extend(&DatasetPath{[]string{t.Component}})
thisVisit := DatasetPathVisit{ thisVisit := &DatasetPathVisit{
&DatasetPath{this}, thisVisitPath,
t.FilledIn, t.FilledIn,
parent,
} }
visitChildTree := visitor(thisVisit) visitChildTree := visitor(thisVisit)
if visitChildTree { if visitChildTree {
for _, c := range t.Children { for _, c := range t.Children {
c.WalkTopDown(this, visitor) c.WalkTopDown(thisVisit, visitor)
} }
} }

View File

@ -28,8 +28,8 @@ func makeVisitRecorder() (v DatasetPathsVisitor, rec *visitRecorder) {
rec = &visitRecorder{ rec = &visitRecorder{
visits: make([]DatasetPathVisit, 0), visits: make([]DatasetPathVisit, 0),
} }
v = func(v DatasetPathVisit) bool { v = func(v *DatasetPathVisit) bool {
rec.visits = append(rec.visits, v) rec.visits = append(rec.visits, *v)
return true return true
} }
return return
@ -43,6 +43,27 @@ func buildForest(paths []*DatasetPath) (f *DatasetPathForest) {
return return
} }
type expectedDatasetPathVisit struct {
expectPath *DatasetPath
expectFillIn bool
}
func (e *expectedDatasetPathVisit) AssertEqual(t *testing.T, actual DatasetPathVisit) {
assert.Equal(t, e.expectPath, actual.Path)
assert.Equal(t, e.expectFillIn, actual.FilledIn)
if actual.Parent != nil {
assert.Equal(t, actual.Parent.Path.Length()+1, e.expectPath.Length())
assert.True(t, e.expectPath.HasPrefix(actual.Parent.Path))
}
}
func expectedDatasetPathVisits(t *testing.T, expected []expectedDatasetPathVisit, actual []DatasetPathVisit) {
assert.Equal(t, len(expected), len(actual))
for i := range expected {
expected[i].AssertEqual(t, actual[i])
}
}
func TestDatasetPathForestWalkTopDown(t *testing.T) { func TestDatasetPathForestWalkTopDown(t *testing.T) {
paths := []*DatasetPath{ paths := []*DatasetPath{
@ -56,7 +77,7 @@ func TestDatasetPathForestWalkTopDown(t *testing.T) {
buildForest(paths).WalkTopDown(v) buildForest(paths).WalkTopDown(v)
expectedVisits := []DatasetPathVisit{ expectedVisits := []expectedDatasetPathVisit{
{toDatasetPath("pool1"), false}, {toDatasetPath("pool1"), false},
{toDatasetPath("pool1/foo"), true}, {toDatasetPath("pool1/foo"), true},
{toDatasetPath("pool1/foo/bar"), false}, {toDatasetPath("pool1/foo/bar"), false},
@ -65,8 +86,7 @@ func TestDatasetPathForestWalkTopDown(t *testing.T) {
{toDatasetPath("pool2/test"), true}, {toDatasetPath("pool2/test"), true},
{toDatasetPath("pool2/test/bar"), false}, {toDatasetPath("pool2/test/bar"), false},
} }
assert.Equal(t, expectedVisits, rec.visits) expectedDatasetPathVisits(t, expectedVisits, rec.visits)
} }
func TestDatasetPathWalkTopDownWorksUnordered(t *testing.T) { func TestDatasetPathWalkTopDownWorksUnordered(t *testing.T) {
@ -82,7 +102,7 @@ func TestDatasetPathWalkTopDownWorksUnordered(t *testing.T) {
buildForest(paths).WalkTopDown(v) buildForest(paths).WalkTopDown(v)
expectedVisits := []DatasetPathVisit{ expectedVisits := []expectedDatasetPathVisit{
{toDatasetPath("pool1"), false}, {toDatasetPath("pool1"), false},
{toDatasetPath("pool1/foo"), true}, {toDatasetPath("pool1/foo"), true},
{toDatasetPath("pool1/foo/bar"), false}, {toDatasetPath("pool1/foo/bar"), false},
@ -91,6 +111,6 @@ func TestDatasetPathWalkTopDownWorksUnordered(t *testing.T) {
{toDatasetPath("pool1/bang/baz"), false}, {toDatasetPath("pool1/bang/baz"), false},
} }
assert.Equal(t, expectedVisits, rec.visits) expectedDatasetPathVisits(t, expectedVisits, rec.visits)
} }

View File

@ -6,6 +6,7 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/pkg/errors"
"github.com/zrepl/zrepl/zfs/zfscmd" "github.com/zrepl/zrepl/zfs/zfscmd"
) )
@ -78,14 +79,23 @@ func ZFSGetFilesystemPlaceholderState(ctx context.Context, p *DatasetPath) (stat
return state, nil return state, nil
} }
func ZFSCreatePlaceholderFilesystem(ctx context.Context, p *DatasetPath) (err error) { func ZFSCreatePlaceholderFilesystem(ctx context.Context, fs *DatasetPath, parent *DatasetPath) (err error) {
if p.Length() == 1 { if fs.Length() == 1 {
return fmt.Errorf("cannot create %q: pools cannot be created with zfs create", p.ToString()) return fmt.Errorf("cannot create %q: pools cannot be created with zfs create", fs.ToString())
} }
cmd := zfscmd.CommandContext(ctx, ZFS_BINARY, "create",
cmdline := []string{
"create",
"-o", fmt.Sprintf("%s=%s", PlaceholderPropertyName, placeholderPropertyOn), "-o", fmt.Sprintf("%s=%s", PlaceholderPropertyName, placeholderPropertyOn),
"-o", "mountpoint=none", "-o", "mountpoint=none",
p.ToString()) }
if parentEncrypted, err := ZFSGetEncryptionEnabled(ctx, parent.ToString()); err != nil {
return errors.Wrap(err, "cannot determine encryption support")
} else if parentEncrypted {
cmdline = append(cmdline, "-o", "encryption=off")
}
cmdline = append(cmdline, fs.ToString())
cmd := zfscmd.CommandContext(ctx, ZFS_BINARY, cmdline...)
stdio, err := cmd.CombinedOutput() stdio, err := cmd.CombinedOutput()
if err != nil { if err != nil {