360 lines
7.9 KiB
Go
360 lines
7.9 KiB
Go
|
package zfs_test
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"math"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"reflect"
|
||
|
"runtime"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/mistifyio/go-zfs"
|
||
|
)
|
||
|
|
||
|
func sleep(delay int) {
|
||
|
time.Sleep(time.Duration(delay) * time.Second)
|
||
|
}
|
||
|
|
||
|
func pow2(x int) int64 {
|
||
|
return int64(math.Pow(2, float64(x)))
|
||
|
}
|
||
|
|
||
|
//https://github.com/benbjohnson/testing
|
||
|
// assert fails the test if the condition is false.
|
||
|
func assert(tb testing.TB, condition bool, msg string, v ...interface{}) {
|
||
|
if !condition {
|
||
|
_, file, line, _ := runtime.Caller(1)
|
||
|
fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...)
|
||
|
tb.FailNow()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ok fails the test if an err is not nil.
|
||
|
func ok(tb testing.TB, err error) {
|
||
|
if err != nil {
|
||
|
_, file, line, _ := runtime.Caller(1)
|
||
|
fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error())
|
||
|
tb.FailNow()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// equals fails the test if exp is not equal to act.
|
||
|
func equals(tb testing.TB, exp, act interface{}) {
|
||
|
if !reflect.DeepEqual(exp, act) {
|
||
|
_, file, line, _ := runtime.Caller(1)
|
||
|
fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act)
|
||
|
tb.FailNow()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func zpoolTest(t *testing.T, fn func()) {
|
||
|
tempfiles := make([]string, 3)
|
||
|
for i := range tempfiles {
|
||
|
f, _ := ioutil.TempFile("/tmp/", "zfs-")
|
||
|
defer f.Close()
|
||
|
err := f.Truncate(pow2(30))
|
||
|
ok(t, err)
|
||
|
tempfiles[i] = f.Name()
|
||
|
defer os.Remove(f.Name())
|
||
|
}
|
||
|
|
||
|
pool, err := zfs.CreateZpool("test", nil, tempfiles...)
|
||
|
ok(t, err)
|
||
|
defer pool.Destroy()
|
||
|
ok(t, err)
|
||
|
fn()
|
||
|
|
||
|
}
|
||
|
|
||
|
func TestDatasets(t *testing.T) {
|
||
|
zpoolTest(t, func() {
|
||
|
_, err := zfs.Datasets("")
|
||
|
ok(t, err)
|
||
|
|
||
|
ds, err := zfs.GetDataset("test")
|
||
|
ok(t, err)
|
||
|
equals(t, zfs.DatasetFilesystem, ds.Type)
|
||
|
equals(t, "", ds.Origin)
|
||
|
assert(t, ds.Logicalused > 0, "Logicalused is not greater than 0")
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestSnapshots(t *testing.T) {
|
||
|
|
||
|
zpoolTest(t, func() {
|
||
|
snapshots, err := zfs.Snapshots("")
|
||
|
ok(t, err)
|
||
|
|
||
|
for _, snapshot := range snapshots {
|
||
|
equals(t, zfs.DatasetSnapshot, snapshot.Type)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestFilesystems(t *testing.T) {
|
||
|
zpoolTest(t, func() {
|
||
|
f, err := zfs.CreateFilesystem("test/filesystem-test", nil)
|
||
|
ok(t, err)
|
||
|
|
||
|
filesystems, err := zfs.Filesystems("")
|
||
|
ok(t, err)
|
||
|
|
||
|
for _, filesystem := range filesystems {
|
||
|
equals(t, zfs.DatasetFilesystem, filesystem.Type)
|
||
|
}
|
||
|
|
||
|
ok(t, f.Destroy(zfs.DestroyDefault))
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestCreateFilesystemWithProperties(t *testing.T) {
|
||
|
zpoolTest(t, func() {
|
||
|
props := map[string]string{
|
||
|
"compression": "lz4",
|
||
|
}
|
||
|
|
||
|
f, err := zfs.CreateFilesystem("test/filesystem-test", props)
|
||
|
ok(t, err)
|
||
|
|
||
|
equals(t, "lz4", f.Compression)
|
||
|
|
||
|
filesystems, err := zfs.Filesystems("")
|
||
|
ok(t, err)
|
||
|
|
||
|
for _, filesystem := range filesystems {
|
||
|
equals(t, zfs.DatasetFilesystem, filesystem.Type)
|
||
|
}
|
||
|
|
||
|
ok(t, f.Destroy(zfs.DestroyDefault))
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestVolumes(t *testing.T) {
|
||
|
zpoolTest(t, func() {
|
||
|
v, err := zfs.CreateVolume("test/volume-test", uint64(pow2(23)), nil)
|
||
|
ok(t, err)
|
||
|
|
||
|
// volumes are sometimes "busy" if you try to manipulate them right away
|
||
|
sleep(1)
|
||
|
|
||
|
equals(t, zfs.DatasetVolume, v.Type)
|
||
|
volumes, err := zfs.Volumes("")
|
||
|
ok(t, err)
|
||
|
|
||
|
for _, volume := range volumes {
|
||
|
equals(t, zfs.DatasetVolume, volume.Type)
|
||
|
}
|
||
|
|
||
|
ok(t, v.Destroy(zfs.DestroyDefault))
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestSnapshot(t *testing.T) {
|
||
|
zpoolTest(t, func() {
|
||
|
f, err := zfs.CreateFilesystem("test/snapshot-test", nil)
|
||
|
ok(t, err)
|
||
|
|
||
|
filesystems, err := zfs.Filesystems("")
|
||
|
ok(t, err)
|
||
|
|
||
|
for _, filesystem := range filesystems {
|
||
|
equals(t, zfs.DatasetFilesystem, filesystem.Type)
|
||
|
}
|
||
|
|
||
|
s, err := f.Snapshot("test", false)
|
||
|
ok(t, err)
|
||
|
|
||
|
equals(t, zfs.DatasetSnapshot, s.Type)
|
||
|
|
||
|
equals(t, "test/snapshot-test@test", s.Name)
|
||
|
|
||
|
ok(t, s.Destroy(zfs.DestroyDefault))
|
||
|
|
||
|
ok(t, f.Destroy(zfs.DestroyDefault))
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestClone(t *testing.T) {
|
||
|
zpoolTest(t, func() {
|
||
|
f, err := zfs.CreateFilesystem("test/snapshot-test", nil)
|
||
|
ok(t, err)
|
||
|
|
||
|
filesystems, err := zfs.Filesystems("")
|
||
|
ok(t, err)
|
||
|
|
||
|
for _, filesystem := range filesystems {
|
||
|
equals(t, zfs.DatasetFilesystem, filesystem.Type)
|
||
|
}
|
||
|
|
||
|
s, err := f.Snapshot("test", false)
|
||
|
ok(t, err)
|
||
|
|
||
|
equals(t, zfs.DatasetSnapshot, s.Type)
|
||
|
equals(t, "test/snapshot-test@test", s.Name)
|
||
|
|
||
|
c, err := s.Clone("test/clone-test", nil)
|
||
|
ok(t, err)
|
||
|
|
||
|
equals(t, zfs.DatasetFilesystem, c.Type)
|
||
|
|
||
|
ok(t, c.Destroy(zfs.DestroyDefault))
|
||
|
|
||
|
ok(t, s.Destroy(zfs.DestroyDefault))
|
||
|
|
||
|
ok(t, f.Destroy(zfs.DestroyDefault))
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestSendSnapshot(t *testing.T) {
|
||
|
zpoolTest(t, func() {
|
||
|
f, err := zfs.CreateFilesystem("test/snapshot-test", nil)
|
||
|
ok(t, err)
|
||
|
|
||
|
filesystems, err := zfs.Filesystems("")
|
||
|
ok(t, err)
|
||
|
|
||
|
for _, filesystem := range filesystems {
|
||
|
equals(t, zfs.DatasetFilesystem, filesystem.Type)
|
||
|
}
|
||
|
|
||
|
s, err := f.Snapshot("test", false)
|
||
|
ok(t, err)
|
||
|
|
||
|
file, _ := ioutil.TempFile("/tmp/", "zfs-")
|
||
|
defer file.Close()
|
||
|
err = file.Truncate(pow2(30))
|
||
|
ok(t, err)
|
||
|
defer os.Remove(file.Name())
|
||
|
|
||
|
err = s.SendSnapshot(file)
|
||
|
ok(t, err)
|
||
|
|
||
|
ok(t, s.Destroy(zfs.DestroyDefault))
|
||
|
|
||
|
ok(t, f.Destroy(zfs.DestroyDefault))
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestChildren(t *testing.T) {
|
||
|
zpoolTest(t, func() {
|
||
|
f, err := zfs.CreateFilesystem("test/snapshot-test", nil)
|
||
|
ok(t, err)
|
||
|
|
||
|
s, err := f.Snapshot("test", false)
|
||
|
ok(t, err)
|
||
|
|
||
|
equals(t, zfs.DatasetSnapshot, s.Type)
|
||
|
equals(t, "test/snapshot-test@test", s.Name)
|
||
|
|
||
|
children, err := f.Children(0)
|
||
|
ok(t, err)
|
||
|
|
||
|
equals(t, 1, len(children))
|
||
|
equals(t, "test/snapshot-test@test", children[0].Name)
|
||
|
|
||
|
ok(t, s.Destroy(zfs.DestroyDefault))
|
||
|
ok(t, f.Destroy(zfs.DestroyDefault))
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestListZpool(t *testing.T) {
|
||
|
zpoolTest(t, func() {
|
||
|
pools, err := zfs.ListZpools()
|
||
|
ok(t, err)
|
||
|
equals(t, "test", pools[0].Name)
|
||
|
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestRollback(t *testing.T) {
|
||
|
zpoolTest(t, func() {
|
||
|
f, err := zfs.CreateFilesystem("test/snapshot-test", nil)
|
||
|
ok(t, err)
|
||
|
|
||
|
filesystems, err := zfs.Filesystems("")
|
||
|
ok(t, err)
|
||
|
|
||
|
for _, filesystem := range filesystems {
|
||
|
equals(t, zfs.DatasetFilesystem, filesystem.Type)
|
||
|
}
|
||
|
|
||
|
s1, err := f.Snapshot("test", false)
|
||
|
ok(t, err)
|
||
|
|
||
|
_, err = f.Snapshot("test2", false)
|
||
|
ok(t, err)
|
||
|
|
||
|
s3, err := f.Snapshot("test3", false)
|
||
|
ok(t, err)
|
||
|
|
||
|
err = s3.Rollback(false)
|
||
|
ok(t, err)
|
||
|
|
||
|
err = s1.Rollback(false)
|
||
|
assert(t, err != nil, "should error when rolling back beyond most recent without destroyMoreRecent = true")
|
||
|
|
||
|
err = s1.Rollback(true)
|
||
|
ok(t, err)
|
||
|
|
||
|
ok(t, s1.Destroy(zfs.DestroyDefault))
|
||
|
|
||
|
ok(t, f.Destroy(zfs.DestroyDefault))
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestDiff(t *testing.T) {
|
||
|
zpoolTest(t, func() {
|
||
|
fs, err := zfs.CreateFilesystem("test/origin", nil)
|
||
|
ok(t, err)
|
||
|
|
||
|
linkedFile, err := os.Create(filepath.Join(fs.Mountpoint, "linked"))
|
||
|
ok(t, err)
|
||
|
|
||
|
movedFile, err := os.Create(filepath.Join(fs.Mountpoint, "file"))
|
||
|
ok(t, err)
|
||
|
|
||
|
snapshot, err := fs.Snapshot("snapshot", false)
|
||
|
ok(t, err)
|
||
|
|
||
|
unicodeFile, err := os.Create(filepath.Join(fs.Mountpoint, "i ❤ unicode"))
|
||
|
ok(t, err)
|
||
|
|
||
|
err = os.Rename(movedFile.Name(), movedFile.Name()+"-new")
|
||
|
ok(t, err)
|
||
|
|
||
|
err = os.Link(linkedFile.Name(), linkedFile.Name()+"_hard")
|
||
|
ok(t, err)
|
||
|
|
||
|
inodeChanges, err := fs.Diff(snapshot.Name)
|
||
|
ok(t, err)
|
||
|
equals(t, 4, len(inodeChanges))
|
||
|
|
||
|
equals(t, "/test/origin/", inodeChanges[0].Path)
|
||
|
equals(t, zfs.Directory, inodeChanges[0].Type)
|
||
|
equals(t, zfs.Modified, inodeChanges[0].Change)
|
||
|
|
||
|
equals(t, "/test/origin/linked", inodeChanges[1].Path)
|
||
|
equals(t, zfs.File, inodeChanges[1].Type)
|
||
|
equals(t, zfs.Modified, inodeChanges[1].Change)
|
||
|
equals(t, 1, inodeChanges[1].ReferenceCountChange)
|
||
|
|
||
|
equals(t, "/test/origin/file", inodeChanges[2].Path)
|
||
|
equals(t, "/test/origin/file-new", inodeChanges[2].NewPath)
|
||
|
equals(t, zfs.File, inodeChanges[2].Type)
|
||
|
equals(t, zfs.Renamed, inodeChanges[2].Change)
|
||
|
|
||
|
equals(t, "/test/origin/i ❤ unicode", inodeChanges[3].Path)
|
||
|
equals(t, zfs.File, inodeChanges[3].Type)
|
||
|
equals(t, zfs.Created, inodeChanges[3].Change)
|
||
|
|
||
|
ok(t, movedFile.Close())
|
||
|
ok(t, unicodeFile.Close())
|
||
|
ok(t, linkedFile.Close())
|
||
|
ok(t, snapshot.Destroy(zfs.DestroyForceUmount))
|
||
|
ok(t, fs.Destroy(zfs.DestroyForceUmount))
|
||
|
})
|
||
|
}
|