Functions to set hold, release and list user references on snapshots
This commit is contained in:
parent
bcd0988597
commit
0d2d2cf113
|
@ -25,6 +25,7 @@ func Test(t *testing.T) {
|
||||||
zfsTestDatasetSnapshot(t)
|
zfsTestDatasetSnapshot(t)
|
||||||
zfsTestDatasetOpenAll(t)
|
zfsTestDatasetOpenAll(t)
|
||||||
zfsTestDatasetSetProperty(t)
|
zfsTestDatasetSetProperty(t)
|
||||||
|
zfsTestDatasetHoldRelease(t)
|
||||||
|
|
||||||
zfsTestDatasetDestroy(t)
|
zfsTestDatasetDestroy(t)
|
||||||
|
|
||||||
|
|
117
zfs.go
117
zfs.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,6 +38,12 @@ const (
|
||||||
DatasetTypeBookmark = (1 << 4)
|
DatasetTypeBookmark = (1 << 4)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// HoldTag - user holds tags
|
||||||
|
type HoldTag struct {
|
||||||
|
Name string
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// Dataset - ZFS dataset object
|
// Dataset - ZFS dataset object
|
||||||
type Dataset struct {
|
type Dataset struct {
|
||||||
list C.dataset_list_ptr
|
list C.dataset_list_ptr
|
||||||
|
@ -99,6 +106,16 @@ func DatasetCloseAll(datasets []Dataset) {
|
||||||
|
|
||||||
// DatasetOpen open dataset and all of its recursive children datasets
|
// DatasetOpen open dataset and all of its recursive children datasets
|
||||||
func DatasetOpen(path string) (d Dataset, err error) {
|
func DatasetOpen(path string) (d Dataset, err error) {
|
||||||
|
if d, err = DatasetOpenSingle(path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = d.openChildren()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatasetOpenSingle open dataset without opening all of its recursive
|
||||||
|
// children datasets
|
||||||
|
func DatasetOpenSingle(path string) (d Dataset, err error) {
|
||||||
csPath := C.CString(path)
|
csPath := C.CString(path)
|
||||||
d.list = C.dataset_open(csPath)
|
d.list = C.dataset_open(csPath)
|
||||||
C.free(unsafe.Pointer(csPath))
|
C.free(unsafe.Pointer(csPath))
|
||||||
|
@ -117,7 +134,6 @@ func DatasetOpen(path string) (d Dataset, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = d.openChildren()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +217,16 @@ func (d *Dataset) Destroy(Defer bool) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSnapshot - retrun true if datset is snapshot
|
||||||
|
func (d *Dataset) IsSnapshot() (ok bool, err error) {
|
||||||
|
var path string
|
||||||
|
if path, err = d.Path(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok = d.Type == DatasetTypeSnapshot || strings.Contains(path, "@")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DestroyRecursive recursively destroy children of dataset and dataset.
|
// DestroyRecursive recursively destroy children of dataset and dataset.
|
||||||
func (d *Dataset) DestroyRecursive() (err error) {
|
func (d *Dataset) DestroyRecursive() (err error) {
|
||||||
var path string
|
var path string
|
||||||
|
@ -306,6 +332,7 @@ func (d *Dataset) GetProperty(p Prop) (prop Property, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserProperty - lookup and return user propery
|
||||||
func (d *Dataset) GetUserProperty(p string) (prop Property, err error) {
|
func (d *Dataset) GetUserProperty(p string) (prop Property, err error) {
|
||||||
if d.list == nil {
|
if d.list == nil {
|
||||||
err = errors.New(msgDatasetIsNil)
|
err = errors.New(msgDatasetIsNil)
|
||||||
|
@ -345,6 +372,7 @@ func (d *Dataset) SetProperty(p Prop, value string) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetUserProperty -
|
||||||
func (d *Dataset) SetUserProperty(prop, value string) (err error) {
|
func (d *Dataset) SetUserProperty(prop, value string) (err error) {
|
||||||
if d.list == nil {
|
if d.list == nil {
|
||||||
err = errors.New(msgDatasetIsNil)
|
err = errors.New(msgDatasetIsNil)
|
||||||
|
@ -517,6 +545,93 @@ func (d *Dataset) UnmountAll(flags int) (err error) {
|
||||||
return d.Unmount(flags)
|
return d.Unmount(flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hold - Adds a single reference, named with the tag argument, to the snapshot.
|
||||||
|
// Each snapshot has its own tag namespace, and tags must be unique within that space.
|
||||||
|
func (d *Dataset) Hold(flag string) (err error) {
|
||||||
|
var path string
|
||||||
|
var pd Dataset
|
||||||
|
if path, err = d.Path(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.Contains(path, "@") {
|
||||||
|
err = fmt.Errorf("'%s' is not a snapshot", path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pd, err = DatasetOpenSingle(path[:strings.Index(path, "@")])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer pd.Close()
|
||||||
|
csSnapName := C.CString(path[strings.Index(path, "@")+1:])
|
||||||
|
defer C.free(unsafe.Pointer(csSnapName))
|
||||||
|
csFlag := C.CString(flag)
|
||||||
|
defer C.free(unsafe.Pointer(csFlag))
|
||||||
|
if 0 != C.zfs_hold(pd.list.zh, csSnapName, csFlag, booleanT(false), -1) {
|
||||||
|
err = LastError()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release - Removes a single reference, named with the tag argument, from the specified snapshot.
|
||||||
|
// The tag must already exist for each snapshot. If a hold exists on a snapshot, attempts to destroy
|
||||||
|
// that snapshot by using the zfs destroy command return EBUSY.
|
||||||
|
func (d *Dataset) Release(flag string) (err error) {
|
||||||
|
var path string
|
||||||
|
var pd Dataset
|
||||||
|
if path, err = d.Path(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.Contains(path, "@") {
|
||||||
|
err = fmt.Errorf("'%s' is not a snapshot", path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pd, err = DatasetOpenSingle(path[:strings.Index(path, "@")])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer pd.Close()
|
||||||
|
csSnapName := C.CString(path[strings.Index(path, "@")+1:])
|
||||||
|
defer C.free(unsafe.Pointer(csSnapName))
|
||||||
|
csFlag := C.CString(flag)
|
||||||
|
defer C.free(unsafe.Pointer(csFlag))
|
||||||
|
if 0 != C.zfs_release(pd.list.zh, csSnapName, csFlag, booleanT(false)) {
|
||||||
|
err = LastError()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holds - Lists all existing user references for the given snapshot
|
||||||
|
func (d *Dataset) Holds() (tags []HoldTag, err error) {
|
||||||
|
var nvl *C.nvlist_t
|
||||||
|
var nvp *C.nvpair_t
|
||||||
|
var tu64 C.uint64_t
|
||||||
|
var path string
|
||||||
|
if path, err = d.Path(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.Contains(path, "@") {
|
||||||
|
err = fmt.Errorf("'%s' is not a snapshot", path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if 0 != C.zfs_get_holds(d.list.zh, &nvl) {
|
||||||
|
err = LastError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer C.nvlist_free(nvl)
|
||||||
|
tags = make([]HoldTag, 0, 5)
|
||||||
|
for nvp = C.nvlist_next_nvpair(nvl, nvp); nvp != nil; {
|
||||||
|
tag := C.nvpair_name(nvp)
|
||||||
|
C.nvpair_value_uint64(nvp, &tu64)
|
||||||
|
tags = append(tags, HoldTag{
|
||||||
|
Name: C.GoString(tag),
|
||||||
|
Timestamp: time.Unix(int64(tu64), 0),
|
||||||
|
})
|
||||||
|
|
||||||
|
nvp = C.nvlist_next_nvpair(nvl, nvp)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DatasetPropertyToName convert property to name
|
// DatasetPropertyToName convert property to name
|
||||||
// ( returns built in string representation of property name).
|
// ( returns built in string representation of property name).
|
||||||
// This is optional, you can represent each property with string
|
// This is optional, you can represent each property with string
|
||||||
|
|
41
zfs_test.go
41
zfs_test.go
|
@ -147,6 +147,47 @@ func zfsTestDatasetSnapshot(t *testing.T) {
|
||||||
print("PASS\n\n")
|
print("PASS\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func zfsTestDatasetHoldRelease(t *testing.T) {
|
||||||
|
println("TEST Hold/Release(", TSTDatasetPathSnap, ", true, ...) ... ")
|
||||||
|
d, err := zfs.DatasetOpen(TSTDatasetPathSnap)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer d.Close()
|
||||||
|
err = d.Hold("keep")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tags []zfs.HoldTag
|
||||||
|
tags, err = d.Holds()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, tag := range tags {
|
||||||
|
println("tag:", tag.Name, "timestamp:", tag.Timestamp.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Release("keep")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tags, err = d.Holds()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, tag := range tags {
|
||||||
|
println("* tag:", tag.Name, "timestamp:", tag.Timestamp.String())
|
||||||
|
}
|
||||||
|
print("PASS\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
func zfsTestDatasetDestroy(t *testing.T) {
|
func zfsTestDatasetDestroy(t *testing.T) {
|
||||||
println("TEST DATASET Destroy( ", TSTDatasetPath, " ) ... ")
|
println("TEST DATASET Destroy( ", TSTDatasetPath, " ) ... ")
|
||||||
d, err := zfs.DatasetOpen(TSTDatasetPath)
|
d, err := zfs.DatasetOpen(TSTDatasetPath)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package zfs_test
|
package zfs_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -531,33 +530,33 @@ func ExamplePool_State() {
|
||||||
println("POOL TESTPOOL state:", zfs.PoolStateToName(pstate))
|
println("POOL TESTPOOL state:", zfs.PoolStateToName(pstate))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPool_VDevTree(t *testing.T) {
|
// func TestPool_VDevTree(t *testing.T) {
|
||||||
type fields struct {
|
// type fields struct {
|
||||||
poolName string
|
// poolName string
|
||||||
}
|
// }
|
||||||
tests := []struct {
|
// tests := []struct {
|
||||||
name string
|
// name string
|
||||||
fields fields
|
// fields fields
|
||||||
wantErr bool
|
// wantErr bool
|
||||||
}{
|
// }{
|
||||||
// TODO: Add test cases.
|
// // TODO: Add test cases.
|
||||||
{
|
// {
|
||||||
name: "test1",
|
// name: "test1",
|
||||||
fields: fields{"TESTPOOL"},
|
// fields: fields{"TESTPOOL"},
|
||||||
wantErr: false,
|
// wantErr: false,
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
for _, tt := range tests {
|
// for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
// t.Run(tt.name, func(t *testing.T) {
|
||||||
pool, _ := zfs.PoolOpen(tt.fields.poolName)
|
// pool, _ := zfs.PoolOpen(tt.fields.poolName)
|
||||||
defer pool.Close()
|
// defer pool.Close()
|
||||||
gotVdevs, err := pool.VDevTree()
|
// gotVdevs, err := pool.VDevTree()
|
||||||
if (err != nil) != tt.wantErr {
|
// if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("Pool.VDevTree() error = %v, wantErr %v", err, tt.wantErr)
|
// t.Errorf("Pool.VDevTree() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
jsonData, _ := json.MarshalIndent(gotVdevs, "", "\t")
|
// jsonData, _ := json.MarshalIndent(gotVdevs, "", "\t")
|
||||||
t.Logf("gotVdevs: %s", string(jsonData))
|
// t.Logf("gotVdevs: %s", string(jsonData))
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
Loading…
Reference in New Issue