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)
 | 
			
		||||
	zfsTestDatasetOpenAll(t)
 | 
			
		||||
	zfsTestDatasetSetProperty(t)
 | 
			
		||||
	zfsTestDatasetHoldRelease(t)
 | 
			
		||||
 | 
			
		||||
	zfsTestDatasetDestroy(t)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										117
									
								
								zfs.go
								
								
								
								
							
							
						
						
									
										117
									
								
								zfs.go
								
								
								
								
							| 
						 | 
				
			
			@ -11,6 +11,7 @@ import (
 | 
			
		|||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	"unsafe"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +38,12 @@ const (
 | 
			
		|||
	DatasetTypeBookmark = (1 << 4)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HoldTag - user holds  tags
 | 
			
		||||
type HoldTag struct {
 | 
			
		||||
	Name      string
 | 
			
		||||
	Timestamp time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Dataset - ZFS dataset object
 | 
			
		||||
type Dataset struct {
 | 
			
		||||
	list       C.dataset_list_ptr
 | 
			
		||||
| 
						 | 
				
			
			@ -99,6 +106,16 @@ func DatasetCloseAll(datasets []Dataset) {
 | 
			
		|||
 | 
			
		||||
// DatasetOpen open dataset and all of its recursive children datasets
 | 
			
		||||
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)
 | 
			
		||||
	d.list = C.dataset_open(csPath)
 | 
			
		||||
	C.free(unsafe.Pointer(csPath))
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +134,6 @@ func DatasetOpen(path string) (d Dataset, err error) {
 | 
			
		|||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err = d.openChildren()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -201,6 +217,16 @@ func (d *Dataset) Destroy(Defer bool) (err error) {
 | 
			
		|||
	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.
 | 
			
		||||
func (d *Dataset) DestroyRecursive() (err error) {
 | 
			
		||||
	var path string
 | 
			
		||||
| 
						 | 
				
			
			@ -306,6 +332,7 @@ func (d *Dataset) GetProperty(p Prop) (prop Property, err error) {
 | 
			
		|||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUserProperty - lookup and return user propery
 | 
			
		||||
func (d *Dataset) GetUserProperty(p string) (prop Property, err error) {
 | 
			
		||||
	if d.list == nil {
 | 
			
		||||
		err = errors.New(msgDatasetIsNil)
 | 
			
		||||
| 
						 | 
				
			
			@ -345,6 +372,7 @@ func (d *Dataset) SetProperty(p Prop, value string) (err error) {
 | 
			
		|||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetUserProperty -
 | 
			
		||||
func (d *Dataset) SetUserProperty(prop, value string) (err error) {
 | 
			
		||||
	if d.list == nil {
 | 
			
		||||
		err = errors.New(msgDatasetIsNil)
 | 
			
		||||
| 
						 | 
				
			
			@ -517,6 +545,93 @@ func (d *Dataset) UnmountAll(flags int) (err error) {
 | 
			
		|||
	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
 | 
			
		||||
// ( returns built in string representation of property name).
 | 
			
		||||
// 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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
	println("TEST DATASET Destroy( ", TSTDatasetPath, " ) ... ")
 | 
			
		||||
	d, err := zfs.DatasetOpen(TSTDatasetPath)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
package zfs_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
| 
						 | 
				
			
			@ -531,33 +530,33 @@ func ExamplePool_State() {
 | 
			
		|||
	println("POOL TESTPOOL state:", zfs.PoolStateToName(pstate))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPool_VDevTree(t *testing.T) {
 | 
			
		||||
	type fields struct {
 | 
			
		||||
		poolName string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		fields  fields
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
		{
 | 
			
		||||
			name:    "test1",
 | 
			
		||||
			fields:  fields{"TESTPOOL"},
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			pool, _ := zfs.PoolOpen(tt.fields.poolName)
 | 
			
		||||
			defer pool.Close()
 | 
			
		||||
			gotVdevs, err := pool.VDevTree()
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("Pool.VDevTree() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			jsonData, _ := json.MarshalIndent(gotVdevs, "", "\t")
 | 
			
		||||
			t.Logf("gotVdevs: %s", string(jsonData))
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
// func TestPool_VDevTree(t *testing.T) {
 | 
			
		||||
// 	type fields struct {
 | 
			
		||||
// 		poolName string
 | 
			
		||||
// 	}
 | 
			
		||||
// 	tests := []struct {
 | 
			
		||||
// 		name    string
 | 
			
		||||
// 		fields  fields
 | 
			
		||||
// 		wantErr bool
 | 
			
		||||
// 	}{
 | 
			
		||||
// 		// TODO: Add test cases.
 | 
			
		||||
// 		{
 | 
			
		||||
// 			name:    "test1",
 | 
			
		||||
// 			fields:  fields{"TESTPOOL"},
 | 
			
		||||
// 			wantErr: false,
 | 
			
		||||
// 		},
 | 
			
		||||
// 	}
 | 
			
		||||
// 	for _, tt := range tests {
 | 
			
		||||
// 		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
// 			pool, _ := zfs.PoolOpen(tt.fields.poolName)
 | 
			
		||||
// 			defer pool.Close()
 | 
			
		||||
// 			gotVdevs, err := pool.VDevTree()
 | 
			
		||||
// 			if (err != nil) != tt.wantErr {
 | 
			
		||||
// 				t.Errorf("Pool.VDevTree() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
// 				return
 | 
			
		||||
// 			}
 | 
			
		||||
// 			jsonData, _ := json.MarshalIndent(gotVdevs, "", "\t")
 | 
			
		||||
// 			t.Logf("gotVdevs: %s", string(jsonData))
 | 
			
		||||
// 		})
 | 
			
		||||
// 	}
 | 
			
		||||
// }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue