- Implemented: Search pools available to import but not imported AND Fetch imported pool's current vdev tree

This commit is contained in:
Faruk Kasumovic 2015-12-10 21:29:39 +01:00
parent 4f32480fa0
commit b49a2715c2
6 changed files with 438 additions and 26 deletions

View File

@ -9,7 +9,9 @@ import (
func Test(t *testing.T) { func Test(t *testing.T) {
zpoolTestPoolCreate(t) zpoolTestPoolCreate(t)
zpoolTestPoolVDevTree(t)
zpoolTestExport(t) zpoolTestExport(t)
zpoolTestPoolImportSearch(t)
zpoolTestImport(t) zpoolTestImport(t)
zpoolTestExportForce(t) zpoolTestExportForce(t)
zpoolTestImportByGUID(t) zpoolTestImportByGUID(t)

View File

@ -57,6 +57,12 @@ type PoolStatus int
// PoolState type representing pool state // PoolState type representing pool state
type PoolState uint64 type PoolState uint64
// VDevState - vdev states tye
type VDevState uint64
// VDevAux - vdev aux states
type VDevAux uint64
// Property ZFS pool or dataset property value // Property ZFS pool or dataset property value
type Property struct { type Property struct {
Value string Value string
@ -346,3 +352,37 @@ const (
EPoolreadonly /* pool is in read-only mode */ EPoolreadonly /* pool is in read-only mode */
EUnknown EUnknown
) )
// vdev states are ordered from least to most healthy.
// A vdev that's VDevStateCantOpen or below is considered unusable.
const (
VDevStateUnknown VDevState = iota // Uninitialized vdev
VDevStateClosed // Not currently open
VDevStateOffline // Not allowed to open
VDevStateRemoved // Explicitly removed from system
VDevStateCantOpen // Tried to open, but failed
VDevStateFaulted // External request to fault device
VDevStateDegraded // Replicated vdev with unhealthy kids
VDevStateHealthy // Presumed good
)
// vdev aux states. When a vdev is in the VDevStateCantOpen state, the aux field
// of the vdev stats structure uses these constants to distinguish why.
const (
VDevAuxNone VDevAux = iota // no error
VDevAuxOpenFailed // ldi_open_*() or vn_open() failed
VDevAuxCorruptData // bad label or disk contents
VDevAuxNoReplicas // insufficient number of replicas
VDevAuxBadGUIDSum // vdev guid sum doesn't match
VDevAuxTooSmall // vdev size is too small
VDevAuxBadLabel // the label is OK but invalid
VDevAuxVersionNewer // on-disk version is too new
VDevAuxVersionOlder // on-disk version is too old
VDevAuxUnsupFeat // unsupported features
VDevAuxSpared // hot spare used in another pool
VDevAuxErrExceeded // too many errors
VDevAuxIOFailure // experienced I/O failure
VDevAuxBadLog // cannot read log chain(s)
VDevAuxExternal // external diagnosis
VDevAuxSplitPool // vdev was split off into another pool
)

22
zpool.c
View File

@ -1,7 +1,7 @@
/* C wrappers around some zfs calls and C in general that should simplify /* C wrappers around some zfs calls and C in general that should simplify
* using libzfs from go language, and make go code shorter and more readable. * using libzfs from go language, and make go code shorter and more readable.
*/ */
#include <libzfs.h> #include <libzfs.h>
#include <memory.h> #include <memory.h>
#include <string.h> #include <string.h>
@ -112,17 +112,17 @@ void zprop_source_tostr(char *dst, zprop_source_t source) {
break; break;
default: default:
strcpy(dst, "default"); strcpy(dst, "default");
break; break;
} }
} }
int read_zpool_property(zpool_handle_t *zh, property_list_t *list, int prop) { int read_zpool_property(zpool_handle_t *zh, property_list_t *list, int prop) {
int r = 0; int r = 0;
zprop_source_t source; zprop_source_t source;
r = zpool_get_prop(zh, prop, r = zpool_get_prop(zh, prop,
list->value, INT_MAX_VALUE, &source); list->value, INT_MAX_VALUE, &source);
if (r == 0) { if (r == 0) {
// strcpy(list->name, zpool_prop_to_name(prop)); // strcpy(list->name, zpool_prop_to_name(prop));
@ -366,6 +366,16 @@ add_prop_list(const char *propname, char *propval, nvlist_t **props,
return (0); return (0);
} }
int nvlist_lookup_uint64_array_vds(nvlist_t *nv, const char *p,
vdev_stat_t **vds, uint_t *c) {
return nvlist_lookup_uint64_array(nv, p, (uint64_t**)vds, c);
}
int nvlist_lookup_uint64_array_ps(nvlist_t *nv, const char *p,
pool_scan_stat_t **vds, uint_t *c) {
return nvlist_lookup_uint64_array(nv, p, (uint64_t**)vds, c);
}
nvlist_t** nvlist_alloc_array(int count) { nvlist_t** nvlist_alloc_array(int count) {
return malloc(count*sizeof(nvlist_t*)); return malloc(count*sizeof(nvlist_t*));
} }
@ -381,3 +391,7 @@ void nvlist_free_array(nvlist_t **a) {
void free_cstring(char *str) { void free_cstring(char *str) {
free(str); free(str);
} }
nvlist_t *nvlist_array_at(nvlist_t **a, uint_t i) {
return a[i];
}

333
zpool.go
View File

@ -20,12 +20,94 @@ const (
// PoolProperties type is map of pool properties name -> value // PoolProperties type is map of pool properties name -> value
type PoolProperties map[Prop]string type PoolProperties map[Prop]string
/*
* ZIO types. Needed to interpret vdev statistics below.
*/
const (
ZIOTypeNull = iota
ZIOTypeRead
ZIOTypeWrite
ZIOTypeFree
ZIOTypeClaim
ZIOTypeIOCtl
ZIOTypes
)
// Scan states
const (
DSSNone = iota // No scan
DSSScanning // Scanning
DSSFinished // Scan finished
DSSCanceled // Scan canceled
DSSNumStates // Total number of scan states
)
// Scan functions
const (
PoolScanNone = iota // No scan function
PoolScanScrub // Pools is checked against errors
PoolScanResilver // Pool is resilvering
PoolScanFuncs // Number of scan functions
)
// VDevStat - Vdev statistics. Note: all fields should be 64-bit because this
// is passed between kernel and userland as an nvlist uint64 array.
type VDevStat struct {
Timestamp time.Duration /* time since vdev load (nanoseconds)*/
State VDevState /* vdev state */
Aux VDevAux /* see vdev_aux_t */
Alloc uint64 /* space allocated */
Space uint64 /* total capacity */
DSpace uint64 /* deflated capacity */
RSize uint64 /* replaceable dev size */
ESize uint64 /* expandable dev size */
Ops [ZIOTypes]uint64 /* operation count */
Bytes [ZIOTypes]uint64 /* bytes read/written */
ReadErrors uint64 /* read errors */
WriteErrors uint64 /* write errors */
ChecksumErrors uint64 /* checksum errors */
SelfHealed uint64 /* self-healed bytes */
ScanRemoving uint64 /* removing? */
ScanProcessed uint64 /* scan processed bytes */
Fragmentation uint64 /* device fragmentation */
}
// PoolScanStat - Pool scan statistics
type PoolScanStat struct {
// Values stored on disk
Func uint64 // Current scan function e.g. none, scrub ...
State uint64 // Current scan state e.g. scanning, finished ...
StartTime uint64 // Scan start time
EndTime uint64 // Scan end time
ToExamine uint64 // Total bytes to scan
Examined uint64 // Total bytes scaned
ToProcess uint64 // Total bytes to processed
Processed uint64 // Total bytes processed
Errors uint64 // Scan errors
// Values not stored on disk
PassExam uint64 // Examined bytes per scan pass
PassStart uint64 // Start time of scan pass
}
// VDevTree ZFS virtual device tree // VDevTree ZFS virtual device tree
type VDevTree struct { type VDevTree struct {
Type VDevType Type VDevType
Devices []VDevTree // groups other devices (e.g. mirror) Devices []VDevTree // groups other devices (e.g. mirror)
Parity uint Parity uint
Path string Path string
Name string
Stat VDevStat
ScanStat PoolScanStat
}
// ExportedPool is type representing ZFS pool available for import
type ExportedPool struct {
VDevs VDevTree
Name string
Comment string
GUID uint64
State PoolState
Status PoolStatus
} }
// Pool object represents handler to single ZFS pool // Pool object represents handler to single ZFS pool
@ -56,6 +138,164 @@ func PoolOpen(name string) (pool Pool, err error) {
return return
} }
func poolGetConfig(name string, nv *C.nvlist_t) (vdevs VDevTree, err error) {
var dtype *C.char
var c, children C.uint_t
var notpresent C.uint64_t
var vs *C.vdev_stat_t
var ps *C.pool_scan_stat_t
var child **C.nvlist_t
var vdev VDevTree
if 0 != C.nvlist_lookup_string(nv, C.CString(C.ZPOOL_CONFIG_TYPE), &dtype) {
err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_TYPE)
return
}
vdevs.Name = name
vdevs.Type = VDevType(C.GoString(dtype))
if vdevs.Type == VDevTypeMissing || vdevs.Type == VDevTypeHole {
return
}
// Fetch vdev state
if 0 != C.nvlist_lookup_uint64_array_vds(nv, C.CString(C.ZPOOL_CONFIG_VDEV_STATS),
&vs, &c) {
err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_VDEV_STATS)
return
}
vdevs.Stat.Timestamp = time.Duration(vs.vs_timestamp)
vdevs.Stat.State = VDevState(vs.vs_state)
vdevs.Stat.Aux = VDevAux(vs.vs_aux)
vdevs.Stat.Alloc = uint64(vs.vs_alloc)
vdevs.Stat.Space = uint64(vs.vs_space)
vdevs.Stat.DSpace = uint64(vs.vs_dspace)
vdevs.Stat.RSize = uint64(vs.vs_rsize)
vdevs.Stat.ESize = uint64(vs.vs_esize)
for z := 0; z < ZIOTypes; z++ {
vdev.Stat.Ops[z] = uint64(vs.vs_ops[z])
vdev.Stat.Bytes[z] = uint64(vs.vs_bytes[z])
}
vdevs.Stat.ReadErrors = uint64(vs.vs_read_errors)
vdevs.Stat.WriteErrors = uint64(vs.vs_write_errors)
vdevs.Stat.ChecksumErrors = uint64(vs.vs_checksum_errors)
vdevs.Stat.SelfHealed = uint64(vs.vs_self_healed)
vdevs.Stat.ScanRemoving = uint64(vs.vs_scan_removing)
vdevs.Stat.ScanProcessed = uint64(vs.vs_scan_processed)
vdevs.Stat.Fragmentation = uint64(vs.vs_fragmentation)
// Fetch vdev scan stats
if 0 == C.nvlist_lookup_uint64_array_ps(nv, C.CString(C.ZPOOL_CONFIG_SCAN_STATS),
&ps, &c) {
vdevs.ScanStat.Func = uint64(ps.pss_func)
vdevs.ScanStat.State = uint64(ps.pss_state)
vdevs.ScanStat.StartTime = uint64(ps.pss_start_time)
vdevs.ScanStat.EndTime = uint64(ps.pss_end_time)
vdevs.ScanStat.ToExamine = uint64(ps.pss_to_examine)
vdevs.ScanStat.Examined = uint64(ps.pss_examined)
vdevs.ScanStat.ToProcess = uint64(ps.pss_to_process)
vdevs.ScanStat.Processed = uint64(ps.pss_processed)
vdevs.ScanStat.Errors = uint64(ps.pss_errors)
vdevs.ScanStat.PassExam = uint64(ps.pss_pass_exam)
vdevs.ScanStat.PassStart = uint64(ps.pss_pass_start)
}
// Fetch the children
if C.nvlist_lookup_nvlist_array(nv, C.CString(C.ZPOOL_CONFIG_CHILDREN),
&child, &children) != 0 {
return
}
if children > 0 {
vdevs.Devices = make([]VDevTree, 0, children)
}
if C.nvlist_lookup_uint64(nv, C.CString(C.ZPOOL_CONFIG_NOT_PRESENT),
&notpresent) == 0 {
var path *C.char
if 0 != C.nvlist_lookup_string(nv, C.CString(C.ZPOOL_CONFIG_PATH), &path) {
err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_PATH)
return
}
vdevs.Path = C.GoString(path)
}
for c = 0; c < children; c++ {
var islog = C.uint64_t(C.B_FALSE)
C.nvlist_lookup_uint64(C.nvlist_array_at(child, c),
C.CString(C.ZPOOL_CONFIG_IS_LOG), &islog)
if islog != C.B_FALSE {
continue
}
vname := C.zpool_vdev_name(libzfsHandle, nil, C.nvlist_array_at(child, c),
C.B_TRUE)
vdev, err = poolGetConfig(C.GoString(vname),
C.nvlist_array_at(child, c))
C.free_cstring(vname)
if err != nil {
return
}
vdevs.Devices = append(vdevs.Devices, vdev)
}
return
}
// PoolImportSearch - Search pools available to import but not imported.
// Returns array of found pools.
func PoolImportSearch(searchpaths []string) (epools []ExportedPool, err error) {
var config, nvroot *C.nvlist_t
var cname, msgid, comment *C.char
var poolState, guid C.uint64_t
var reason C.zpool_status_t
var errata C.zpool_errata_t
config = nil
var elem *C.nvpair_t
numofp := len(searchpaths)
cpaths := C.alloc_strings(C.int(numofp))
for i, path := range searchpaths {
C.strings_setat(cpaths, C.int(i), C.CString(path))
}
pools := C.zpool_find_import(libzfsHandle, C.int(numofp), cpaths)
defer C.nvlist_free(pools)
elem = C.nvlist_next_nvpair(pools, elem)
epools = make([]ExportedPool, 0, 1)
for ; elem != nil; elem = C.nvlist_next_nvpair(pools, elem) {
ep := ExportedPool{}
if C.nvpair_value_nvlist(elem, &config) != 0 {
err = LastError()
return
}
if C.nvlist_lookup_uint64(config, C.CString(C.ZPOOL_CONFIG_POOL_STATE),
&poolState) != 0 {
err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_POOL_STATE)
return
}
ep.State = PoolState(poolState)
if C.nvlist_lookup_string(config, C.CString(C.ZPOOL_CONFIG_POOL_NAME), &cname) != 0 {
err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_POOL_NAME)
return
}
ep.Name = C.GoString(cname)
if C.nvlist_lookup_uint64(config, C.CString(C.ZPOOL_CONFIG_POOL_GUID), &guid) != 0 {
err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_POOL_GUID)
return
}
ep.GUID = uint64(guid)
reason = C.zpool_import_status(config, &msgid, &errata)
ep.Status = PoolStatus(reason)
if C.nvlist_lookup_string(config, C.CString(C.ZPOOL_CONFIG_COMMENT), &comment) == 0 {
ep.Comment = C.GoString(comment)
}
if C.nvlist_lookup_nvlist(config, C.CString(C.ZPOOL_CONFIG_VDEV_TREE),
&nvroot) != 0 {
err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_VDEV_TREE)
return
}
ep.VDevs, err = poolGetConfig(ep.Name, nvroot)
epools = append(epools, ep)
}
return
}
func poolSearchImport(q string, searchpaths []string, guid bool) (name string, func poolSearchImport(q string, searchpaths []string, guid bool) (name string,
err error) { err error) {
var config *C.nvlist_t var config *C.nvlist_t
@ -583,23 +823,8 @@ func PoolCreate(name string, vdevs []VDevTree, features map[string]string,
return return
} }
// It can happen that pool is not immediately available, // Open created pool and return handle
// we know we just created it with success so lets wait and retry pool, err = PoolOpen(name)
// but only in case EZFS_NOENT error
retr := 0
for pool, err = PoolOpen(name); err != nil && retr < 3; retr++ {
errno := C.libzfs_errno(libzfsHandle)
if errno == ENoent {
time.Sleep(500 * time.Millisecond)
} else {
err = errors.New(err.Error() + " (PoolOpen)")
return
}
pool, err = PoolOpen(name)
}
if err != nil {
err = errors.New(err.Error() + " (PoolOpen)")
}
return return
} }
@ -654,3 +879,69 @@ func (pool *Pool) ExportForce(log string) (err error) {
} }
return return
} }
// VDevTree - Fetch pool's current vdev tree configuration, state and stats
func (pool *Pool) VDevTree() (vdevs VDevTree, err error) {
var nvroot *C.nvlist_t
var poolName string
config := C.zpool_get_config(pool.list.zph, nil)
if config == nil {
err = fmt.Errorf("Failed zpool_get_config")
return
}
if C.nvlist_lookup_nvlist(config, C.CString(C.ZPOOL_CONFIG_VDEV_TREE),
&nvroot) != 0 {
err = fmt.Errorf("Failed to fetch %s", C.ZPOOL_CONFIG_VDEV_TREE)
return
}
if poolName, err = pool.Name(); err != nil {
return
}
return poolGetConfig(poolName, nvroot)
}
func (s PoolState) String() string {
switch s {
case PoolStateActive:
return "ACTIVE"
case PoolStateExported:
return "EXPORTED"
case PoolStateDestroyed:
return "DESTROYED"
case PoolStateSpare:
return "SPARE"
case PoolStateL2cache:
return "L2CACHE"
case PoolStateUninitialized:
return "UNINITIALIZED"
case PoolStateUnavail:
return "UNAVAILABLE"
case PoolStatePotentiallyActive:
return "POTENTIALLYACTIVE"
default:
return "UNKNOWN"
}
}
func (s VDevState) String() string {
switch s {
case VDevStateUnknown:
return "UNINITIALIZED"
case VDevStateClosed:
return "CLOSED"
case VDevStateOffline:
return "OFFLINE"
case VDevStateRemoved:
return "REMOVED"
case VDevStateCantOpen:
return "CANT_OPEN"
case VDevStateFaulted:
return "FAULTED"
case VDevStateDegraded:
return "DEGRADED"
case VDevStateHealthy:
return "ONLINE"
default:
return "UNKNOWN"
}
}

View File

@ -50,9 +50,16 @@ add_prop_list(const char *propname, char *propval, nvlist_t **props,
nvlist_t** nvlist_alloc_array(int count); nvlist_t** nvlist_alloc_array(int count);
void nvlist_array_set(nvlist_t** a, int i, nvlist_t *item); void nvlist_array_set(nvlist_t** a, int i, nvlist_t *item);
void nvlist_free_array(nvlist_t **a); void nvlist_free_array(nvlist_t **a);
nvlist_t *nvlist_array_at(nvlist_t **a, uint_t i);
void free_cstring(char *str); void free_cstring(char *str);
int nvlist_lookup_uint64_array_vds(nvlist_t *nv, const char *p,
vdev_stat_t **vds, uint_t *c);
int nvlist_lookup_uint64_array_ps(nvlist_t *nv, const char *p,
pool_scan_stat_t **vds, uint_t *c);
#endif #endif
/* SERVERWARE_ZPOOL_H */ /* SERVERWARE_ZPOOL_H */

View File

@ -224,6 +224,36 @@ func zpoolTestImportByGUID(t *testing.T) {
print("PASS\n\n") print("PASS\n\n")
} }
func printVDevTree(vt zfs.VDevTree, pref string) {
first := pref + vt.Name
fmt.Printf("%-30s | %-10s | %-10s | %s\n", first, vt.Type,
vt.Stat.State.String(), vt.Path)
for _, v := range vt.Devices {
printVDevTree(v, " "+pref)
}
}
func zpoolTestPoolImportSearch(t *testing.T) {
println("TEST PoolImportSearch")
pools, err := zfs.PoolImportSearch([]string{"/tmp"})
if err != nil {
t.Error(err.Error())
return
}
for _, p := range pools {
println()
println("---------------------------------------------------------------")
println("pool: ", p.Name)
println("guid: ", p.GUID)
println("state: ", p.State.String())
fmt.Printf("%-30s | %-10s | %-10s | %s\n", "NAME", "TYPE", "STATE", "PATH")
println("---------------------------------------------------------------")
printVDevTree(p.VDevs, "")
}
print("PASS\n\n")
}
func zpoolTestPoolProp(t *testing.T) { func zpoolTestPoolProp(t *testing.T) {
println("TEST PoolProp on ", TSTPoolName, " ... ") println("TEST PoolProp on ", TSTPoolName, " ... ")
if pool, err := zfs.PoolOpen(TSTPoolName); err == nil { if pool, err := zfs.PoolOpen(TSTPoolName); err == nil {
@ -237,11 +267,19 @@ func zpoolTestPoolProp(t *testing.T) {
} }
// Test fetching property // Test fetching property
_, err := pool.GetProperty(zfs.PoolPropHealth) propHealth, err := pool.GetProperty(zfs.PoolPropHealth)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
println("Pool property health: ", propHealth.Value)
propGUID, err := pool.GetProperty(zfs.PoolPropGUID)
if err != nil {
t.Error(err)
return
}
println("Pool property GUID: ", propGUID.Value)
// this test pool should not be bootable // this test pool should not be bootable
prop, err := pool.GetProperty(zfs.PoolPropBootfs) prop, err := pool.GetProperty(zfs.PoolPropBootfs)
@ -290,6 +328,26 @@ func zpoolTestPoolStatusAndState(t *testing.T) {
print("PASS\n\n") print("PASS\n\n")
} }
func zpoolTestPoolVDevTree(t *testing.T) {
var vdevs zfs.VDevTree
println("TEST pool VDevTree ( ", TSTPoolName, " ) ... ")
pool, err := zfs.PoolOpen(TSTPoolName)
if err != nil {
t.Error(err.Error())
return
}
defer pool.Close()
vdevs, err = pool.VDevTree()
if err != nil {
t.Error(err.Error())
return
}
fmt.Printf("%-30s | %-10s | %-10s | %s\n", "NAME", "TYPE", "STATE", "PATH")
println("---------------------------------------------------------------")
printVDevTree(vdevs, "")
print("PASS\n\n")
}
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
// EXAMPLES: // EXAMPLES: