zfs: implement DatasetPath top down walk

This commit is contained in:
Christian Schwarz 2017-05-07 14:07:50 +02:00
parent 2407556f15
commit c762502f6e
2 changed files with 217 additions and 0 deletions

122
zfs/datasetpath_visitor.go Normal file
View 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
}

View 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)
}