diff --git a/a_test.go b/a_test.go index 568c34b..28f9aad 100644 --- a/a_test.go +++ b/a_test.go @@ -9,7 +9,9 @@ import ( func Test(t *testing.T) { zpoolTestPoolCreate(t) + zpoolTestPoolVDevTree(t) zpoolTestExport(t) + zpoolTestPoolImportSearch(t) zpoolTestImport(t) zpoolTestExportForce(t) zpoolTestImportByGUID(t) diff --git a/common.go b/common.go index d89a3e5..002a2b5 100644 --- a/common.go +++ b/common.go @@ -57,6 +57,12 @@ type PoolStatus int // PoolState type representing pool state type PoolState uint64 +// VDevState - vdev states tye +type VDevState uint64 + +// VDevAux - vdev aux states +type VDevAux uint64 + // Property ZFS pool or dataset property value type Property struct { Value string @@ -346,3 +352,37 @@ const ( EPoolreadonly /* pool is in read-only mode */ 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 +) diff --git a/zpool.c b/zpool.c index 54eddbf..3488531 100644 --- a/zpool.c +++ b/zpool.c @@ -1,7 +1,7 @@ /* 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. */ - + #include #include #include @@ -112,17 +112,17 @@ void zprop_source_tostr(char *dst, zprop_source_t source) { break; default: strcpy(dst, "default"); - break; + break; } } int read_zpool_property(zpool_handle_t *zh, property_list_t *list, int prop) { - + int r = 0; zprop_source_t source; - r = zpool_get_prop(zh, prop, + r = zpool_get_prop(zh, prop, list->value, INT_MAX_VALUE, &source); if (r == 0) { // 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); } +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) { return malloc(count*sizeof(nvlist_t*)); } @@ -381,3 +391,7 @@ void nvlist_free_array(nvlist_t **a) { void free_cstring(char *str) { free(str); } + +nvlist_t *nvlist_array_at(nvlist_t **a, uint_t i) { + return a[i]; +} diff --git a/zpool.go b/zpool.go index c63cc62..dca41da 100644 --- a/zpool.go +++ b/zpool.go @@ -20,12 +20,94 @@ const ( // PoolProperties type is map of pool properties name -> value 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 type VDevTree struct { - Type VDevType - Devices []VDevTree // groups other devices (e.g. mirror) - Parity uint - Path string + Type VDevType + Devices []VDevTree // groups other devices (e.g. mirror) + Parity uint + 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 @@ -56,6 +138,164 @@ func PoolOpen(name string) (pool Pool, err error) { 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), + ¬present) == 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, err error) { var config *C.nvlist_t @@ -583,23 +823,8 @@ func PoolCreate(name string, vdevs []VDevTree, features map[string]string, return } - // It can happen that pool is not immediately available, - // we know we just created it with success so lets wait and retry - // 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)") - } + // Open created pool and return handle + pool, err = PoolOpen(name) return } @@ -654,3 +879,69 @@ func (pool *Pool) ExportForce(log string) (err error) { } 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" + } +} diff --git a/zpool.h b/zpool.h index 04698ae..93b6dbe 100644 --- a/zpool.h +++ b/zpool.h @@ -50,9 +50,16 @@ add_prop_list(const char *propname, char *propval, nvlist_t **props, nvlist_t** nvlist_alloc_array(int count); void nvlist_array_set(nvlist_t** a, int i, nvlist_t *item); void nvlist_free_array(nvlist_t **a); +nvlist_t *nvlist_array_at(nvlist_t **a, uint_t i); 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 /* SERVERWARE_ZPOOL_H */ diff --git a/zpool_test.go b/zpool_test.go index f3406f5..54d16f1 100644 --- a/zpool_test.go +++ b/zpool_test.go @@ -224,6 +224,36 @@ func zpoolTestImportByGUID(t *testing.T) { 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) { println("TEST PoolProp on ", TSTPoolName, " ... ") if pool, err := zfs.PoolOpen(TSTPoolName); err == nil { @@ -237,11 +267,19 @@ func zpoolTestPoolProp(t *testing.T) { } // Test fetching property - _, err := pool.GetProperty(zfs.PoolPropHealth) + propHealth, err := pool.GetProperty(zfs.PoolPropHealth) if err != nil { t.Error(err) 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 prop, err := pool.GetProperty(zfs.PoolPropBootfs) @@ -290,6 +328,26 @@ func zpoolTestPoolStatusAndState(t *testing.T) { 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: