diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go index 96ad595..e06bfc3 100644 --- a/endpoint/endpoint.go +++ b/endpoint/endpoint.go @@ -679,7 +679,7 @@ func (s *Receiver) Receive(ctx context.Context, req *pdu.ReceiveReq, receive io. f := zfs.NewDatasetPathForest() f.Add(lp) 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) { 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.Debug("create placeholder filesystem") - err := zfs.ZFSCreatePlaceholderFilesystem(ctx, v.Path) + err := zfs.ZFSCreatePlaceholderFilesystem(ctx, v.Path, v.Parent.Path) if err != nil { l.WithError(err).Error("cannot create placeholder filesystem") visitErr = err diff --git a/zfs/datasetpath_visitor.go b/zfs/datasetpath_visitor.go index 89a7b2a..4a2c342 100644 --- a/zfs/datasetpath_visitor.go +++ b/zfs/datasetpath_visitor.go @@ -33,9 +33,10 @@ type DatasetPathVisit struct { Path *DatasetPath // If true, the dataset referenced by Path was not in the list of datasets to traverse 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 // 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) { 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{ - &DatasetPath{this}, + thisVisit := &DatasetPathVisit{ + thisVisitPath, t.FilledIn, + parent, } visitChildTree := visitor(thisVisit) if visitChildTree { for _, c := range t.Children { - c.WalkTopDown(this, visitor) + c.WalkTopDown(thisVisit, visitor) } } diff --git a/zfs/datasetpath_visitor_test.go b/zfs/datasetpath_visitor_test.go index b9e5734..c2c6181 100644 --- a/zfs/datasetpath_visitor_test.go +++ b/zfs/datasetpath_visitor_test.go @@ -28,8 +28,8 @@ func makeVisitRecorder() (v DatasetPathsVisitor, rec *visitRecorder) { rec = &visitRecorder{ visits: make([]DatasetPathVisit, 0), } - v = func(v DatasetPathVisit) bool { - rec.visits = append(rec.visits, v) + v = func(v *DatasetPathVisit) bool { + rec.visits = append(rec.visits, *v) return true } return @@ -43,6 +43,27 @@ func buildForest(paths []*DatasetPath) (f *DatasetPathForest) { 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) { paths := []*DatasetPath{ @@ -56,7 +77,7 @@ func TestDatasetPathForestWalkTopDown(t *testing.T) { buildForest(paths).WalkTopDown(v) - expectedVisits := []DatasetPathVisit{ + expectedVisits := []expectedDatasetPathVisit{ {toDatasetPath("pool1"), false}, {toDatasetPath("pool1/foo"), true}, {toDatasetPath("pool1/foo/bar"), false}, @@ -65,8 +86,7 @@ func TestDatasetPathForestWalkTopDown(t *testing.T) { {toDatasetPath("pool2/test"), true}, {toDatasetPath("pool2/test/bar"), false}, } - assert.Equal(t, expectedVisits, rec.visits) - + expectedDatasetPathVisits(t, expectedVisits, rec.visits) } func TestDatasetPathWalkTopDownWorksUnordered(t *testing.T) { @@ -82,7 +102,7 @@ func TestDatasetPathWalkTopDownWorksUnordered(t *testing.T) { buildForest(paths).WalkTopDown(v) - expectedVisits := []DatasetPathVisit{ + expectedVisits := []expectedDatasetPathVisit{ {toDatasetPath("pool1"), false}, {toDatasetPath("pool1/foo"), true}, {toDatasetPath("pool1/foo/bar"), false}, @@ -91,6 +111,6 @@ func TestDatasetPathWalkTopDownWorksUnordered(t *testing.T) { {toDatasetPath("pool1/bang/baz"), false}, } - assert.Equal(t, expectedVisits, rec.visits) + expectedDatasetPathVisits(t, expectedVisits, rec.visits) } diff --git a/zfs/placeholder.go b/zfs/placeholder.go index 0ffffa8..00700d6 100644 --- a/zfs/placeholder.go +++ b/zfs/placeholder.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" + "github.com/pkg/errors" "github.com/zrepl/zrepl/zfs/zfscmd" ) @@ -78,14 +79,23 @@ func ZFSGetFilesystemPlaceholderState(ctx context.Context, p *DatasetPath) (stat return state, nil } -func ZFSCreatePlaceholderFilesystem(ctx context.Context, p *DatasetPath) (err error) { - if p.Length() == 1 { - return fmt.Errorf("cannot create %q: pools cannot be created with zfs create", p.ToString()) +func ZFSCreatePlaceholderFilesystem(ctx context.Context, fs *DatasetPath, parent *DatasetPath) (err error) { + if fs.Length() == 1 { + 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", "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() if err != nil {