diff --git a/a_test.go b/a_test.go new file mode 100644 index 0000000..fae4a3c --- /dev/null +++ b/a_test.go @@ -0,0 +1,23 @@ +package zfs_test + +import ( + "testing" +) + +/* ------------------------------------------------------------------------- */ +// TESTS ARE DEPENDED AND MUST RUN IN DEPENDENT ORDER + +func Test(t *testing.T) { + zpoolTestPoolCreate(t) + zpoolTestPoolOpenAll(t) + zpoolTestFailPoolOpen(t) + + zfsTestDatasetCreate(t) + zfsTestDatasetOpen(t) + zfsTestDatasetSnapshot(t) + zfsTestDatasetOpenAll(t) + + zfsTestDatasetDestroy(t) + + zpoolTestPoolDestroy(t) +} diff --git a/zfs_test.go b/zfs_test.go new file mode 100644 index 0000000..5f82b3a --- /dev/null +++ b/zfs_test.go @@ -0,0 +1,171 @@ +package zfs_test + +import ( + "fmt" + "github.com/bicomsystems/go-libzfs" + "testing" +) + +/* ------------------------------------------------------------------------- */ +// HELPERS: +var TST_DATASET_PATH = TST_POOL_NAME + "/DATASET" +var TST_VOLUME_PATH = TST_DATASET_PATH + "/VOLUME" +var TST_DATASET_PATH_SNAP = TST_DATASET_PATH + "@test" + +func printDatasets(ds []zfs.Dataset) error { + for _, d := range ds { + + path, err := d.Path() + if err != nil { + return err + } + p, err := d.GetProperty(zfs.ZFSPropType) + if err != nil { + return err + } + fmt.Printf(" %30s | %10s\n", path, + p.Value) + if len(d.Children) > 0 { + printDatasets(d.Children) + } + } + return nil +} + +/* ------------------------------------------------------------------------- */ +// TESTS: + +func zfsTestDatasetCreate(t *testing.T) { + // reinit names used in case TESTPOOL was in conflict + TST_DATASET_PATH = TST_POOL_NAME + "/DATASET" + TST_VOLUME_PATH = TST_DATASET_PATH + "/VOLUME" + TST_DATASET_PATH_SNAP = TST_DATASET_PATH + "@test" + + println("TEST DatasetCreate(", TST_DATASET_PATH, ") (filesystem) ... ") + props := make(map[zfs.ZFSProp]zfs.Property) + d, err := zfs.DatasetCreate(TST_DATASET_PATH, zfs.DatasetTypeFilesystem, props) + if err != nil { + t.Error(err) + return + } + d.Close() + println("PASS\n") + + strSize := "536870912" // 512M + + println("TEST DatasetCreate(", TST_VOLUME_PATH, ") (volume) ... ") + props[zfs.ZFSPropVolsize] = zfs.Property{Value: strSize} + // In addition I explicitly choose some more properties to be set. + props[zfs.ZFSPropVolblocksize] = zfs.Property{Value: "4096"} + props[zfs.ZFSPropReservation] = zfs.Property{Value: strSize} + d, err = zfs.DatasetCreate(TST_VOLUME_PATH, zfs.DatasetTypeVolume, props) + if err != nil { + t.Error(err) + return + } + d.Close() + println("PASS\n") +} + +func zfsTestDatasetOpen(t *testing.T) { + println("TEST DatasetOpen(", TST_DATASET_PATH, ") ... ") + d, err := zfs.DatasetOpen(TST_DATASET_PATH) + if err != nil { + t.Error(err) + return + } + d.Close() + println("PASS\n") +} + +func zfsTestDatasetOpenAll(t *testing.T) { + println("TEST DatasetOpenAll()/DatasetCloseAll() ... ") + ds, err := zfs.DatasetOpenAll() + if err != nil { + t.Error(err) + return + } + if err = printDatasets(ds); err != nil { + zfs.DatasetCloseAll(ds) + t.Error(err) + return + } + zfs.DatasetCloseAll(ds) + println("PASS\n") +} + +func zfsTestDatasetSnapshot(t *testing.T) { + println("TEST DatasetSnapshot(", TST_DATASET_PATH, ", true, ...) ... ") + props := make(map[zfs.ZFSProp]zfs.Property) + d, err := zfs.DatasetSnapshot(TST_DATASET_PATH_SNAP, true, props) + if err != nil { + t.Error(err) + return + } + defer d.Close() + println("PASS\n") +} + +func zfsTestDatasetDestroy(t *testing.T) { + println("TEST DATASET Destroy( ", TST_DATASET_PATH, " ) ... ") + d, err := zfs.DatasetOpen(TST_DATASET_PATH) + if err != nil { + t.Error(err) + return + } + defer d.Close() + if err = d.DestroyRecursive(); err != nil { + t.Error(err) + return + } + println("PASS\n") +} + +/* ------------------------------------------------------------------------- */ +// EXAMPLES: + +// Example of creating ZFS volume +func ExampleDatasetCreate() { + // Create map to represent ZFS dataset properties. This is equivalent to + // list of properties you can get from ZFS CLI tool, and some more + // internally used by libzfs. + props := make(map[zfs.ZFSProp]zfs.Property) + + // I choose to create (block) volume 1GiB in size. Size is just ZFS dataset + // property and this is done as map of strings. So, You have to either + // specify size as base 10 number in string, or use strconv package or + // similar to convert in to string (base 10) from numeric type. + strSize := "1073741824" + + props[zfs.ZFSPropVolsize] = zfs.Property{Value: strSize} + // In addition I explicitly choose some more properties to be set. + props[zfs.ZFSPropVolblocksize] = zfs.Property{Value: "4096"} + props[zfs.ZFSPropReservation] = zfs.Property{Value: strSize} + + // Lets create desired volume + d, err := zfs.DatasetCreate("TESTPOOL/VOLUME1", zfs.DatasetTypeVolume, props) + if err != nil { + println(err.Error()) + return + } + // Dataset have to be closed for memory cleanup + defer d.Close() + + println("Created zfs volume TESTPOOL/VOLUME1") +} + +func ExampleDatasetOpen() { + // Open dataset and read its available space + d, err := zfs.DatasetOpen("TESTPOOL/DATASET1") + if err != nil { + panic(err.Error()) + return + } + defer d.Close() + var p zfs.Property + if p, err = d.GetProperty(zfs.ZFSPropAvailable); err != nil { + panic(err.Error()) + return + } + println(d.PropertyToName(zfs.ZFSPropAvailable), " = ", p.Value) +} diff --git a/zpool_test.go b/zpool_test.go index d5b1afa..3a19add 100644 --- a/zpool_test.go +++ b/zpool_test.go @@ -1,17 +1,17 @@ -package zfs +package zfs_test import ( "fmt" + "github.com/bicomsystems/go-libzfs" "io/ioutil" "os" - "strconv" "testing" ) -const ( - TST_POOL_NAME = "TESTPOOL" - TST_DATASET_PATH = "TESTPOOL/DATASET" -) +/* ------------------------------------------------------------------------- */ +// HELPERS: + +var TST_POOL_NAME = "TESTPOOL" func CreateTmpSparse(prefix string, size int64) (path string, err error) { sf, err := ioutil.TempFile("/tmp", prefix) @@ -26,9 +26,24 @@ func CreateTmpSparse(prefix string, size int64) (path string, err error) { return } -// Create 3 sparse file 5G in /tmp directory each 5G size, and use them to create mirror TESTPOOL with one spare "disk" -func TestPoolCreate(t *testing.T) { - print("TEST PoolCreate ... ") +/* ------------------------------------------------------------------------- */ +// TESTS: + +// Create 3 sparse file in /tmp directory each 5G size, and use them to create +// mirror TESTPOOL with one spare "disk" +func zpoolTestPoolCreate(t *testing.T) { + println("TEST PoolCreate ... ") + // first check if pool with same name already exist + // we don't want conflict + for { + p, err := zfs.PoolOpen(TST_POOL_NAME) + if err != nil { + break + } + p.Close() + TST_POOL_NAME += "0" + } + var s1path, s2path, s3path string var err error if s1path, err = CreateTmpSparse("zfs_test_", 0x140000000); err != nil { @@ -50,27 +65,27 @@ func TestPoolCreate(t *testing.T) { } disks := [2]string{s1path, s2path} - var vdevs, mdevs, sdevs []VDevSpec + var vdevs, mdevs, sdevs []zfs.VDevSpec for _, d := range disks { mdevs = append(mdevs, - VDevSpec{Type: VDevTypeFile, Path: d}) + zfs.VDevSpec{Type: zfs.VDevTypeFile, Path: d}) } - sdevs = []VDevSpec{ - {Type: VDevTypeFile, Path: s3path}} - vdevs = []VDevSpec{ - VDevSpec{Type: VDevTypeMirror, Devices: mdevs}, - VDevSpec{Type: VDevTypeSpare, Devices: sdevs}, + sdevs = []zfs.VDevSpec{ + {Type: zfs.VDevTypeFile, Path: s3path}} + vdevs = []zfs.VDevSpec{ + zfs.VDevSpec{Type: zfs.VDevTypeMirror, Devices: mdevs}, + zfs.VDevSpec{Type: zfs.VDevTypeSpare, Devices: sdevs}, } - props := make(map[PoolProp]string) - fsprops := make(map[ZFSProp]string) + props := make(map[zfs.PoolProp]string) + fsprops := make(map[zfs.ZFSProp]string) features := make(map[string]string) - fsprops[ZFSPropMountpoint] = "none" + fsprops[zfs.ZFSPropMountpoint] = "none" features["async_destroy"] = "enabled" features["empty_bpobj"] = "enabled" features["lz4_compress"] = "enabled" - pool, err := PoolCreate(TST_POOL_NAME, vdevs, features, props, fsprops) + pool, err := zfs.PoolCreate(TST_POOL_NAME, vdevs, features, props, fsprops) if err != nil { t.Error(err) // try cleanup @@ -84,15 +99,15 @@ func TestPoolCreate(t *testing.T) { os.Remove(s1path) os.Remove(s2path) os.Remove(s3path) - println("PASS") + println("PASS\n") } // Open and list all pools and them state on the system // Then list properties of last pool in the list -func TestPoolOpenAll(t *testing.T) { +func zpoolTestPoolOpenAll(t *testing.T) { println("TEST PoolOpenAll() ... ") var pname string - pools, err := PoolOpenAll() + pools, err := zfs.PoolOpenAll() if err != nil { t.Error(err) return @@ -114,123 +129,12 @@ func TestPoolOpenAll(t *testing.T) { println("\tPool: ", pname, " state: ", pstate) p.Close() } - if len(pname) > 0 { - // test open on last pool - println("\tTry to open pool ", pname) - p, err := PoolOpen(pname) - if err != nil { - t.Error(err) - return - } - println("\tOpen pool: ", pname, " success") - println("\t", pname, " PROPERTIES:") - - pc, _ := strconv.Atoi(p.Properties[PoolNumProps].Value) - if len(p.Properties) != (pc + 1) { - p.Close() - t.Error(fmt.Sprint("Number of zpool properties does not match ", - len(p.Properties), " != ", pc+1)) - return - } - for key, value := range p.Properties { - pkey := PoolProp(key) - println("\t\t", p.PropertyToName(pkey), " = ", value.Value, " <- ", value.Source) - } - for key, value := range p.Features { - fmt.Printf("\t feature@%s = %s <- local\n", key, value) - } - if p.Properties[PoolPropListsnaps].Value == "off" { - println("\tlistsnapshots to on") - if err = p.SetProperty(PoolPropListsnaps, "on"); err != nil { - t.Error(err) - } - } else { - println("\tlistsnapshots to off") - if err = p.SetProperty(PoolPropListsnaps, "off"); err != nil { - t.Error(err) - } - } - if err == nil { - println("\tlistsnapshots", "is changed to ", - p.Properties[PoolPropListsnaps].Value, " <- ", - p.Properties[PoolPropListsnaps].Source) - } - p.Close() - } - println("PASS") + println("PASS\n") } -func TestDatasetCreate(t *testing.T) { - print("TEST DatasetCreate(", TST_DATASET_PATH, ") ... ") - props := make(map[ZFSProp]Property) - d, err := DatasetCreate(TST_DATASET_PATH, DatasetTypeFilesystem, props) - if err != nil { - t.Error(err) - return - } - d.Close() - println("PASS") -} - -func TestDatasetOpen(t *testing.T) { - print("TEST DatasetOpen(", TST_DATASET_PATH, ") ... ") - d, err := DatasetOpen(TST_DATASET_PATH) - if err != nil { - t.Error(err) - return - } - d.Close() - println("PASS") -} - -func printDatasets(ds []Dataset) error { - for _, d := range ds { - path, err := d.Path() - if err != nil { - return err - } - println("\t", path) - if len(d.Children) > 0 { - printDatasets(d.Children) - } - } - return nil -} - -func TestDatasetOpenAll(t *testing.T) { - println("TEST DatasetOpenAll()/DatasetCloseAll() ... ") - ds, err := DatasetOpenAll() - if err != nil { - t.Error(err) - return - } - if err = printDatasets(ds); err != nil { - DatasetCloseAll(ds) - t.Error(err) - return - } - DatasetCloseAll(ds) - println("PASS") -} - -func TestDatasetDestroy(t *testing.T) { - print("TEST DATASET Destroy()", TST_DATASET_PATH, " ... ") - d, err := DatasetOpen(TST_DATASET_PATH) - if err != nil { - t.Error(err) - return - } - defer d.Close() - if err = d.Destroy(false); err != nil { - t.Error(err) - return - } - println("PASS") -} - -func TestPoolDestroy(t *testing.T) { - print("TEST POOL Destroy()", TST_POOL_NAME, " ... ") - p, err := PoolOpen(TST_POOL_NAME) +func zpoolTestPoolDestroy(t *testing.T) { + println("TEST POOL Destroy( ", TST_POOL_NAME, " ) ... ") + p, err := zfs.PoolOpen(TST_POOL_NAME) if err != nil { t.Error(err) return @@ -240,15 +144,15 @@ func TestPoolDestroy(t *testing.T) { t.Error(err.Error()) return } - println("PASS") + println("PASS\n") } -func TestFailPoolOpen(t *testing.T) { - print("TEST failing to open pool ... ") +func zpoolTestFailPoolOpen(t *testing.T) { + println("TEST open of non existing pool ... ") pname := "fail to open this pool" - p, err := PoolOpen(pname) + p, err := zfs.PoolOpen(pname) if err != nil { - println("PASS") + println("PASS\n") return } t.Error("PoolOpen pass when it should fail") @@ -256,19 +160,22 @@ func TestFailPoolOpen(t *testing.T) { } func ExamplePoolProp() { - if pool, err := PoolOpen("SSD"); err == nil { - print("Pool size is: ", pool.Properties[PoolPropSize].Value) + if pool, err := zfs.PoolOpen("SSD"); err == nil { + print("Pool size is: ", pool.Properties[zfs.PoolPropSize].Value) // Turn on snapshot listing for pool - pool.SetProperty(PoolPropListsnaps, "on") + pool.SetProperty(zfs.PoolPropListsnaps, "on") } else { print("Error: ", err) } } +/* ------------------------------------------------------------------------- */ +// EXAMPLES: + // Open and list all pools on system with them properties func ExamplePoolOpenAll() { // Lets open handles to all active pools on system - pools, err := PoolOpenAll() + pools, err := zfs.PoolOpenAll() if err != nil { println(err) } @@ -277,15 +184,15 @@ func ExamplePoolOpenAll() { for _, p := range pools { // Print fancy header fmt.Printf("\n -----------------------------------------------------------\n") - fmt.Printf(" POOL: %49s \n", p.Properties[PoolPropName].Value) + fmt.Printf(" POOL: %49s \n", p.Properties[zfs.PoolPropName].Value) fmt.Printf("|-----------------------------------------------------------|\n") fmt.Printf("| PROPERTY | VALUE | SOURCE |\n") fmt.Printf("|-----------------------------------------------------------|\n") // Iterate pool properties and print name, value and source for key, prop := range p.Properties { - pkey := PoolProp(key) - if pkey == PoolPropName { + pkey := zfs.PoolProp(key) + if pkey == zfs.PoolPropName { continue // Skip name its already printed above } fmt.Printf("|%14s | %20s | %15s |\n", p.PropertyToName(pkey), @@ -302,33 +209,33 @@ func ExamplePoolOpenAll() { func ExamplePoolCreate() { disks := [2]string{"/dev/disk/by-id/ATA-123", "/dev/disk/by-id/ATA-456"} - var vdevs, mdevs, sdevs []VDevSpec + var vdevs, mdevs, sdevs []zfs.VDevSpec // build mirror devices specs for _, d := range disks { mdevs = append(mdevs, - VDevSpec{Type: VDevTypeDisk, Path: d}) + zfs.VDevSpec{Type: zfs.VDevTypeDisk, Path: d}) } // spare device specs - sdevs = []VDevSpec{ - {Type: VDevTypeDisk, Path: "/dev/disk/by-id/ATA-789"}} + sdevs = []zfs.VDevSpec{ + {Type: zfs.VDevTypeDisk, Path: "/dev/disk/by-id/ATA-789"}} // pool specs - vdevs = []VDevSpec{ - VDevSpec{Type: VDevTypeMirror, Devices: mdevs}, - VDevSpec{Type: VDevTypeSpare, Devices: sdevs}, + vdevs = []zfs.VDevSpec{ + zfs.VDevSpec{Type: zfs.VDevTypeMirror, Devices: mdevs}, + zfs.VDevSpec{Type: zfs.VDevTypeSpare, Devices: sdevs}, } // pool properties - props := make(map[PoolProp]string) + props := make(map[zfs.PoolProp]string) // root dataset filesystem properties - fsprops := make(map[ZFSProp]string) + fsprops := make(map[zfs.ZFSProp]string) // pool features features := make(map[string]string) // Turn off auto mounting by ZFS - fsprops[ZFSPropMountpoint] = "none" + fsprops[zfs.ZFSPropMountpoint] = "none" // Enable some features features["async_destroy"] = "enabled" @@ -337,7 +244,7 @@ func ExamplePoolCreate() { // Based on specs formed above create test pool as 2 disk mirror and // one spare disk - pool, err := PoolCreate("TESTPOOL", vdevs, features, props, fsprops) + pool, err := zfs.PoolCreate("TESTPOOL", vdevs, features, props, fsprops) if err != nil { println("Error: ", err.Error()) return @@ -349,7 +256,7 @@ func ExamplePool_Destroy() { pname := "TESTPOOL" // Need handle to pool at first place - p, err := PoolOpen(pname) + p, err := zfs.PoolOpen(pname) if err != nil { println("Error: ", err.Error()) return @@ -363,33 +270,3 @@ func ExamplePool_Destroy() { return } } - -// Example of creating ZFS volume -func ExampleDatasetCreate() { - // Create map to represent ZFS dataset properties. This is equivalent to - // list of properties you can get from ZFS CLI tool, and some more - // internally used by libzfs. - props := make(map[ZFSProp]Property) - - // I choose to create (block) volume 1GiB in size. Size is just ZFS dataset - // property and this is done as map of strings. So, You have to either - // specify size as base 10 number in string, or use strconv package or - // similar to convert in to string (base 10) from numeric type. - strSize := "1073741824" - - props[ZFSPropVolsize] = Property{Value: strSize} - // In addition I explicitly choose some more properties to be set. - props[ZFSPropVolblocksize] = Property{Value: "4096"} - props[ZFSPropReservation] = Property{Value: strSize} - - // Lets create desired volume - d, err := DatasetCreate("TESTPOOL/VOLUME1", DatasetTypeVolume, props) - if err != nil { - println(err.Error()) - return - } - // Dataset have to be closed for memory cleanup - defer d.Close() - - println("Created zfs volume TESTPOOL/VOLUME1") -}