diff --git a/libzfs.pyx b/libzfs.pyx index 71efa96..517e7a8 100644 --- a/libzfs.pyx +++ b/libzfs.pyx @@ -475,6 +475,11 @@ class ZFSVdevStatsException(ZFSException): super(ZFSVdevStatsException, self).__init__(code, 'Failed to fetch ZFS Vdev Stats') +class ZFSPoolRaidzExpandStatsException(ZFSException): + def __init__(self, code): + super(ZFSPoolRaidzExpandStatsException, self).__init__(code, 'Failed to retrieve ZFS pool scan stats') + + class ZFSPoolScanStatsException(ZFSException): def __init__(self, code): super(ZFSPoolScanStatsException, self).__init__(code, 'Failed to retrieve ZFS pool scan stats') @@ -2280,7 +2285,7 @@ cdef class ZFSVdev(object): cdef int rv cdef boolean_t rebuild = False - if self.type not in (zfs.VDEV_TYPE_MIRROR, zfs.VDEV_TYPE_DISK, zfs.VDEV_TYPE_FILE): + if self.type not in (zfs.VDEV_TYPE_MIRROR, zfs.VDEV_TYPE_DISK, zfs.VDEV_TYPE_FILE, zfs.VDEV_TYPE_RAIDZ): raise ZFSException(Error.NOTSUP, "Can attach disks to mirrors and stripes only") if self.type == zfs.VDEV_TYPE_MIRROR: @@ -2552,6 +2557,102 @@ cdef class ZFSVdev(object): return result +cdef class ZPoolRaidzExpand(object): + cdef readonly ZFS root + cdef readonly ZFSPool pool + cdef zfs.pool_raidz_expand_stat_t *stats + + def __init__(self, ZFS root, ZFSPool pool): + self.root = root + self.pool = pool + self.stats = NULL + cdef NVList config + cdef NVList nvroot = pool.get_raw_config().get_raw(zfs.ZPOOL_CONFIG_VDEV_TREE) + cdef int ret + cdef uint_t total + if zfs.ZPOOL_CONFIG_SCAN_STATS not in nvroot: + return + + ret = nvroot.nvlist_lookup_uint64_array( + nvroot.handle, zfs.ZPOOL_CONFIG_RAIDZ_EXPAND_STATS, &self.stats, &total + ) + if ret != 0: + raise ZFSPoolRaidzExpandStatsException(ret) + + property state: + def __get__(self): + if self.stats != NULL: + return ScanState(self.stats.pres_state) + + property expanding_vdev: + def __get__(self): + if self.stats != NULL: + return self.stats.pres_expanding_vdev + + property start_time: + def __get__(self): + if self.stats != NULL: + return datetime.utcfromtimestamp(self.stats.pres_start_time) + + property end_time: + def __get__(self): + if self.stats != NULL and self.state != ScanState.SCANNING: + return datetime.utcfromtimestamp(self.stats.pres_end_time) + + property bytes_to_reflow: + def __get__(self): + if self.stats != NULL: + return self.stats.pres_to_reflow + + property bytes_reflowed: + def __get__(self): + if self.stats != NULL: + return self.stats.pres_reflowed + + property waiting_for_resilver: + def __get__(self): + if self.stats != NULL: + return self.stats.pres_waiting_for_resilver + + property total_secs_left: + def __get__(self): + if self.state != ScanState.SCANNING: + return + + copied = self.bytes_reflowed or 1 + total = self.bytes_to_reflow + fraction_done = copied / total + + elapsed = time.time() - self.stats.pres_start_time + elapsed = elapsed or 1 + rate = copied / elapsed + rate = rate or 1 + return int((total - copied) / rate) + + property percentage: + def __get__(self): + if self.stats == NULL: + return + + copied = self.bytes_reflowed or 1 + total = self.bytes_to_reflow + + return (copied / total) * 100 + + def asdict(self): + return { + 'state': self.state.name if self.stats != NULL else None, + 'expanding_vdev': self.expanding_vdev, + 'start_time': self.start_time, + 'end_time': self.end_time, + 'bytes_to_reflow': self.bytes_to_reflow, + 'bytes_reflowed': self.bytes_reflowed, + 'waiting_for_resilver': self.waiting_for_resilver, + 'total_secs_left': self.total_secs_left, + 'percentage': self.percentage, + } + + cdef class ZPoolScrub(object): cdef readonly ZFS root cdef readonly ZFSPool pool @@ -2716,6 +2817,7 @@ cdef class ZFSPool(object): 'properties': {k: p.asdict() for k, p in self.properties.items()} if self.properties else None, 'features': [i.asdict() for i in self.features] if self.features else None, 'scan': self.scrub.asdict(), + 'expand': self.expand.asdict(), 'root_vdev': self.root_vdev.asdict(False), 'groups': { 'data': [i.asdict() for i in self.data_vdevs if i.type not in filter_vdevs], @@ -3057,6 +3159,10 @@ cdef class ZFSPool(object): def __get__(self): return ZPoolScrub(self.root, self) + property expand: + def __get__(self): + return ZPoolRaidzExpand(self.root, self) + IF HAVE_LZC_WAIT: def wait(self, operation_type): if operation_type not in ZpoolWaitActivity.__members__: diff --git a/pxd/zfs.pxd b/pxd/zfs.pxd index 90b2202..657a79e 100644 --- a/pxd/zfs.pxd +++ b/pxd/zfs.pxd @@ -39,6 +39,7 @@ cdef extern from "sys/fs/zfs.h" nogil: const char* ZPOOL_CONFIG_ASHIFT const char* ZPOOL_CONFIG_ASIZE const char* ZPOOL_CONFIG_DTL + const char* ZPOOL_CONFIG_RAIDZ_EXPAND_STATS const char* ZPOOL_CONFIG_SCAN_STATS const char* ZPOOL_CONFIG_VDEV_STATS const char* ZPOOL_CONFIG_WHOLE_DISK @@ -524,6 +525,15 @@ cdef extern from "sys/fs/zfs.h" nogil: uint64_t vs_physical_ashift uint64_t vs_fragmentation + ctypedef struct pool_raidz_expand_stat_t: + uint64_t pres_state # dsl_scan_state_t + uint64_t pres_expanding_vdev + uint64_t pres_start_time + uint64_t pres_end_time + uint64_t pres_to_reflow # bytes that need to be moved + uint64_t pres_reflowed # bytes moved so far + uint64_t pres_waiting_for_resilver + ctypedef struct pool_scan_stat_t: uint64_t pss_func # pool_scan_func_t uint64_t pss_state # dsl_scan_state_t