diff --git a/contrib/pyzfs/libzfs_core/__init__.py b/contrib/pyzfs/libzfs_core/__init__.py index d8c0e44b0..6ad9fa129 100644 --- a/contrib/pyzfs/libzfs_core/__init__.py +++ b/contrib/pyzfs/libzfs_core/__init__.py @@ -63,6 +63,8 @@ from ._libzfs_core import ( lzc_get_holds, lzc_hold, lzc_load_key, + lzc_pool_checkpoint, + lzc_pool_checkpoint_discard, lzc_promote, lzc_receive, lzc_receive_one, @@ -70,6 +72,7 @@ from ._libzfs_core import ( lzc_receive_with_cmdprops, lzc_receive_with_header, lzc_release, + lzc_remap, lzc_reopen, lzc_rollback, lzc_rollback_to, @@ -116,6 +119,8 @@ __all__ = [ 'lzc_get_holds', 'lzc_hold', 'lzc_load_key', + 'lzc_pool_checkpoint', + 'lzc_pool_checkpoint_discard', 'lzc_promote', 'lzc_receive', 'lzc_receive_one', @@ -123,6 +128,7 @@ __all__ = [ 'lzc_receive_with_cmdprops', 'lzc_receive_with_header', 'lzc_release', + 'lzc_remap', 'lzc_reopen', 'lzc_rollback', 'lzc_rollback_to', diff --git a/contrib/pyzfs/libzfs_core/_constants.py b/contrib/pyzfs/libzfs_core/_constants.py index 7bffebd9c..4e1af55d7 100644 --- a/contrib/pyzfs/libzfs_core/_constants.py +++ b/contrib/pyzfs/libzfs_core/_constants.py @@ -57,5 +57,12 @@ zio_encrypt = enum( 'ZIO_CRYPT_AES_192_GCM', 'ZIO_CRYPT_AES_256_GCM' ) +# ZFS-specific error codes +ZFS_ERR_CHECKPOINT_EXISTS = 1024 +ZFS_ERR_DISCARDING_CHECKPOINT = 1025 +ZFS_ERR_NO_CHECKPOINT = 1026 +ZFS_ERR_DEVRM_IN_PROGRESS = 1027 +ZFS_ERR_VDEV_TOO_BIG = 1028 + # vim: softtabstop=4 tabstop=4 expandtab shiftwidth=4 diff --git a/contrib/pyzfs/libzfs_core/_error_translation.py b/contrib/pyzfs/libzfs_core/_error_translation.py index fca67ea89..b9db026d7 100644 --- a/contrib/pyzfs/libzfs_core/_error_translation.py +++ b/contrib/pyzfs/libzfs_core/_error_translation.py @@ -31,7 +31,14 @@ import errno import re import string from . import exceptions as lzc_exc -from ._constants import MAXNAMELEN +from ._constants import ( + MAXNAMELEN, + ZFS_ERR_CHECKPOINT_EXISTS, + ZFS_ERR_DISCARDING_CHECKPOINT, + ZFS_ERR_NO_CHECKPOINT, + ZFS_ERR_DEVRM_IN_PROGRESS, + ZFS_ERR_VDEV_TOO_BIG +) def lzc_create_translate_error(ret, name, ds_type, props): @@ -548,6 +555,32 @@ def lzc_remap_translate_error(ret, name): raise _generic_exception(ret, name, "Failed to remap dataset") +def lzc_pool_checkpoint_translate_error(ret, name, discard=False): + if ret == 0: + return + if ret == errno.ENOENT: + raise lzc_exc.PoolNotFound(name) + if ret == ZFS_ERR_CHECKPOINT_EXISTS: + raise lzc_exc.CheckpointExists() + if ret == ZFS_ERR_NO_CHECKPOINT: + raise lzc_exc.CheckpointNotFound() + if ret == ZFS_ERR_DISCARDING_CHECKPOINT: + raise lzc_exc.CheckpointDiscarding() + if ret == ZFS_ERR_DEVRM_IN_PROGRESS: + raise lzc_exc.DeviceRemovalRunning() + if ret == ZFS_ERR_VDEV_TOO_BIG: + raise lzc_exc.DeviceTooBig() + if discard: + raise _generic_exception( + ret, name, "Failed to discard pool checkpoint") + else: + raise _generic_exception(ret, name, "Failed to create pool checkpoint") + + +def lzc_pool_checkpoint_discard_translate_error(ret, name): + lzc_pool_checkpoint_translate_error(ret, name, discard=True) + + def lzc_rename_translate_error(ret, source, target): if ret == 0: return diff --git a/contrib/pyzfs/libzfs_core/_libzfs_core.py b/contrib/pyzfs/libzfs_core/_libzfs_core.py index ffc930812..db207bf71 100644 --- a/contrib/pyzfs/libzfs_core/_libzfs_core.py +++ b/contrib/pyzfs/libzfs_core/_libzfs_core.py @@ -1574,6 +1574,37 @@ def lzc_remap(name): errors.lzc_remap_translate_error(ret, name) +@_uncommitted() +def lzc_pool_checkpoint(name): + ''' + Creates a checkpoint for the specified pool. + + :param bytes name: the name of the pool to create a checkpoint for. + :raises CheckpointExists: if the pool already has a checkpoint. + :raises CheckpointDiscarding: if ZFS is in the middle of discarding a + checkpoint for this pool. + :raises DeviceRemovalRunning: if a vdev is currently being removed. + :raises DeviceTooBig: if one or more top-level vdevs exceed the maximum + vdev size. + ''' + ret = _lib.lzc_pool_checkpoint(name) + errors.lzc_pool_checkpoint_translate_error(ret, name) + + +@_uncommitted() +def lzc_pool_checkpoint_discard(name): + ''' + Discard the checkpoint from the specified pool. + + :param bytes name: the name of the pool to discard the checkpoint from. + :raises CheckpointNotFound: if pool does not have a checkpoint. + :raises CheckpointDiscarding: if ZFS is in the middle of discarding a + checkpoint for this pool. + ''' + ret = _lib.lzc_pool_checkpoint_discard(name) + errors.lzc_pool_checkpoint_discard_translate_error(ret, name) + + @_uncommitted() def lzc_rename(source, target): ''' diff --git a/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py b/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py index 55899b556..b69c8d779 100644 --- a/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py +++ b/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py @@ -127,6 +127,8 @@ CDEF = """ int lzc_sync(const char *, nvlist_t *, nvlist_t **); int lzc_unload_key(const char *); int lzc_remap(const char *); + int lzc_pool_checkpoint(const char *); + int lzc_pool_checkpoint_discard(const char *); int lzc_rename(const char *, const char *, nvlist_t *, char **); int lzc_destroy_one(const char *fsname, nvlist_t *); diff --git a/contrib/pyzfs/libzfs_core/exceptions.py b/contrib/pyzfs/libzfs_core/exceptions.py index 58e1da6ec..d274b5b06 100644 --- a/contrib/pyzfs/libzfs_core/exceptions.py +++ b/contrib/pyzfs/libzfs_core/exceptions.py @@ -19,6 +19,13 @@ Exceptions that can be raised by libzfs_core operations. """ import errno +from ._constants import ( + ZFS_ERR_CHECKPOINT_EXISTS, + ZFS_ERR_DISCARDING_CHECKPOINT, + ZFS_ERR_NO_CHECKPOINT, + ZFS_ERR_DEVRM_IN_PROGRESS, + ZFS_ERR_VDEV_TOO_BIG +) class ZFSError(Exception): @@ -547,4 +554,29 @@ class ZCPPermissionError(ZCPError): message = "Channel programs must be run as root" +class CheckpointExists(ZFSError): + errno = ZFS_ERR_CHECKPOINT_EXISTS + message = "Pool already has a checkpoint" + + +class CheckpointNotFound(ZFSError): + errno = ZFS_ERR_NO_CHECKPOINT + message = "Pool does not have a checkpoint" + + +class CheckpointDiscarding(ZFSError): + errno = ZFS_ERR_DISCARDING_CHECKPOINT + message = "Pool checkpoint is being discarded" + + +class DeviceRemovalRunning(ZFSError): + errno = ZFS_ERR_DEVRM_IN_PROGRESS + message = "A vdev is currently being removed" + + +class DeviceTooBig(ZFSError): + errno = ZFS_ERR_VDEV_TOO_BIG + message = "One or more top-level vdevs exceed the maximum vdev size" + + # vim: softtabstop=4 tabstop=4 expandtab shiftwidth=4 diff --git a/contrib/pyzfs/libzfs_core/test/test_libzfs_core.py b/contrib/pyzfs/libzfs_core/test/test_libzfs_core.py index 111cd91f9..14303871a 100644 --- a/contrib/pyzfs/libzfs_core/test/test_libzfs_core.py +++ b/contrib/pyzfs/libzfs_core/test/test_libzfs_core.py @@ -3576,6 +3576,42 @@ zfs.sync.snapshot('""" + pool + """@zcp') lzc.lzc_remap(name) + def test_checkpoint(self): + pool = ZFSTest.pool.getRoot().getName() + + lzc.lzc_pool_checkpoint(pool) + + def test_checkpoint_missing_pool(self): + pool = "nonexistent" + + with self.assertRaises(lzc_exc.PoolNotFound): + lzc.lzc_pool_checkpoint(pool) + + def test_checkpoint_already_exists(self): + pool = ZFSTest.pool.getRoot().getName() + + lzc.lzc_pool_checkpoint(pool) + with self.assertRaises(lzc_exc.CheckpointExists): + lzc.lzc_pool_checkpoint(pool) + + def test_checkpoint_discard(self): + pool = ZFSTest.pool.getRoot().getName() + + lzc.lzc_pool_checkpoint(pool) + lzc.lzc_pool_checkpoint_discard(pool) + + def test_checkpoint_discard_missing_pool(self): + pool = "nonexistent" + + with self.assertRaises(lzc_exc.PoolNotFound): + lzc.lzc_pool_checkpoint_discard(pool) + + def test_checkpoint_discard_missing_checkpoint(self): + pool = ZFSTest.pool.getRoot().getName() + + with self.assertRaises(lzc_exc.CheckpointNotFound): + lzc.lzc_pool_checkpoint_discard(pool) + @needs_support(lzc.lzc_list_children) def test_list_children(self): name = ZFSTest.pool.makeName("fs1/fs")