mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-22 00:13:52 +01:00
zfs: implement DatasetPath top down walk
This commit is contained in:
parent
2407556f15
commit
c762502f6e
122
zfs/datasetpath_visitor.go
Normal file
122
zfs/datasetpath_visitor.go
Normal file
@ -0,0 +1,122 @@
|
||||
package zfs
|
||||
|
||||
type DatasetPathForest struct {
|
||||
roots []*datasetPathTree
|
||||
}
|
||||
|
||||
func NewDatasetPathForest() *DatasetPathForest {
|
||||
return &DatasetPathForest{
|
||||
make([]*datasetPathTree, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *DatasetPathForest) Add(p DatasetPath) {
|
||||
if len(p) <= 0 {
|
||||
panic("dataset path too short. must have length > 0")
|
||||
}
|
||||
|
||||
// Find its root
|
||||
var root *datasetPathTree
|
||||
for _, r := range f.roots {
|
||||
if r.Add(p) {
|
||||
root = r
|
||||
break
|
||||
}
|
||||
}
|
||||
if root == nil {
|
||||
root = newDatasetPathTree(p)
|
||||
f.roots = append(f.roots, root)
|
||||
}
|
||||
}
|
||||
|
||||
type DatasetPathVisit struct {
|
||||
Path DatasetPath
|
||||
// If true, the dataset referenced by Path was not in the list of datasets to traverse
|
||||
FilledIn 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.
|
||||
// If there are gaps, i.e. the intermediary component a/b bewtween a and a/b/c,
|
||||
// those gaps are still visited but the FilledIn property of the visit is set to true.
|
||||
func (f *DatasetPathForest) WalkTopDown(visitor DatasetPathsVisitor) {
|
||||
|
||||
for _, r := range f.roots {
|
||||
r.WalkTopDown([]string{}, visitor)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* PRIVATE IMPLEMENTATION */
|
||||
|
||||
type datasetPathTree struct {
|
||||
Component string
|
||||
FilledIn bool
|
||||
Children []*datasetPathTree
|
||||
}
|
||||
|
||||
func (t *datasetPathTree) Add(p DatasetPath) bool {
|
||||
|
||||
if len(p) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if p[0] == t.Component {
|
||||
|
||||
remainder := p[1:]
|
||||
if len(remainder) == 0 {
|
||||
t.FilledIn = false
|
||||
return true
|
||||
}
|
||||
|
||||
for _, c := range t.Children {
|
||||
if c.Add(remainder) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
t.Children = append(t.Children, newDatasetPathTree(remainder))
|
||||
return true
|
||||
|
||||
} else {
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *datasetPathTree) WalkTopDown(parent DatasetPath, visitor DatasetPathsVisitor) {
|
||||
|
||||
this := append(parent, t.Component)
|
||||
|
||||
visitChildTree := visitor(DatasetPathVisit{this, t.FilledIn})
|
||||
|
||||
if visitChildTree {
|
||||
for _, c := range t.Children {
|
||||
c.WalkTopDown(this, visitor)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func newDatasetPathTree(initial DatasetPath) (t *datasetPathTree) {
|
||||
t = &datasetPathTree{}
|
||||
var cur *datasetPathTree
|
||||
cur = t
|
||||
for i, comp := range initial {
|
||||
cur.Component = comp
|
||||
cur.FilledIn = true
|
||||
cur.Children = make([]*datasetPathTree, 0, 1)
|
||||
if i == len(initial)-1 {
|
||||
cur.FilledIn = false // last component is not filled in
|
||||
break
|
||||
}
|
||||
child := &datasetPathTree{}
|
||||
cur.Children = append(cur.Children, child)
|
||||
cur = child
|
||||
}
|
||||
return t
|
||||
}
|
95
zfs/datasetpath_visitor_test.go
Normal file
95
zfs/datasetpath_visitor_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
package zfs
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewDatasetPathTree(t *testing.T) {
|
||||
|
||||
r := newDatasetPathTree(toDatasetPath("pool1/foo/bar"))
|
||||
|
||||
assert.Equal(t, "pool1", r.Component)
|
||||
assert.True(t, len(r.Children) == 1)
|
||||
cr := r.Children[0]
|
||||
assert.Equal(t, "foo", cr.Component)
|
||||
assert.True(t, len(cr.Children) == 1)
|
||||
ccr := cr.Children[0]
|
||||
assert.Equal(t, "bar", ccr.Component)
|
||||
|
||||
}
|
||||
|
||||
type visitRecorder struct {
|
||||
visits []DatasetPathVisit
|
||||
}
|
||||
|
||||
func makeVisitRecorder() (v DatasetPathsVisitor, rec *visitRecorder) {
|
||||
rec = &visitRecorder{
|
||||
visits: make([]DatasetPathVisit, 0),
|
||||
}
|
||||
v = func(v DatasetPathVisit) error {
|
||||
rec.visits = append(rec.visits, v)
|
||||
return nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func buildForest(paths []DatasetPath) (f *DatasetPathForest) {
|
||||
f = NewDatasetPathForest()
|
||||
for _, p := range paths {
|
||||
f.Add(p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestDatasetPathForestWalkTopDown(t *testing.T) {
|
||||
|
||||
paths := []DatasetPath{
|
||||
toDatasetPath("pool1"),
|
||||
toDatasetPath("pool1/foo/bar"),
|
||||
toDatasetPath("pool1/foo/bar/looloo"),
|
||||
toDatasetPath("pool2/test/bar"),
|
||||
}
|
||||
|
||||
v, rec := makeVisitRecorder()
|
||||
|
||||
buildForest(paths).WalkTopDown(v)
|
||||
|
||||
expectedVisists := []DatasetPathVisit{
|
||||
{toDatasetPath("pool1"), false},
|
||||
{toDatasetPath("pool1/foo"), true},
|
||||
{toDatasetPath("pool1/foo/bar"), false},
|
||||
{toDatasetPath("pool1/foo/bar/looloo"), false},
|
||||
{toDatasetPath("pool2"), true},
|
||||
{toDatasetPath("pool2/test"), true},
|
||||
{toDatasetPath("pool2/test/bar"), false},
|
||||
}
|
||||
assert.Equal(t, expectedVisists, rec.visits)
|
||||
|
||||
}
|
||||
|
||||
func TestDatasetPathWalkTopDownWorksUnordered(t *testing.T) {
|
||||
|
||||
paths := []DatasetPath{
|
||||
toDatasetPath("pool1"),
|
||||
toDatasetPath("pool1/foo/bar/looloo"),
|
||||
toDatasetPath("pool1/foo/bar"),
|
||||
toDatasetPath("pool1/bang/baz"),
|
||||
}
|
||||
|
||||
v, rec := makeVisitRecorder()
|
||||
|
||||
buildForest(paths).WalkTopDown(v)
|
||||
|
||||
expectedVisists := []DatasetPathVisit{
|
||||
{toDatasetPath("pool1"), false},
|
||||
{toDatasetPath("pool1/foo"), true},
|
||||
{toDatasetPath("pool1/foo/bar"), false},
|
||||
{toDatasetPath("pool1/foo/bar/looloo"), false},
|
||||
{toDatasetPath("pool1/bang"), true},
|
||||
{toDatasetPath("pool1/bang/baz"), false},
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedVisists, rec.visits)
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user