From 20f94ef24ad4f94099242ab2d643fc320a32f4d8 Mon Sep 17 00:00:00 2001 From: George Shammas Date: Thu, 5 Feb 2026 18:34:55 -0500 Subject: [PATCH] pyzfs: remove unimplemented libzfs_core functions from pyzfs As per #9008, pyzfs implements and documents several functions that would be very useful, but then try to call c functions in libzfs_core. These functions do not exist in libzfs_core, and in the ~7 years of ticket creation still do not exist in libzfs_core. It seems unlikely that these functions will get implemented, though 2 years ago, ~5 years after that ticket lzc_get_props was implemented in 23a489a41167890cdd227366a5f950170df8cc9b which enabled get properties in pyzfs. Sadly the first thing the pyzfs function for lzc_get_props does is call _list, which cals lzc_list, which is not implmented. And the functions to set or inherit properties are still missing. Having these functions in pyzfs are misleading, footguns, and time wasters when evaluating pyzfs. Removing these functions from pyzfs means that _if_ these functions are added in libzfs_core, then pyzfs will also need to re-implement these functions. It's a shame, because these py functions have good documentation and tests. Funny enough the tests are auto skipped if it detects that the functions don't exist in libzfs_core. Reviewed-by: Brian Behlendorf Reviewed-by: Tony Hutter Signed-off-by: George Shammas Closes #9008 Closes #18162 --- contrib/pyzfs/docs/source/index.rst | 3 +- contrib/pyzfs/libzfs_core/__init__.py | 10 - .../pyzfs/libzfs_core/_error_translation.py | 58 --- contrib/pyzfs/libzfs_core/_libzfs_core.py | 350 +----------------- .../pyzfs/libzfs_core/bindings/libzfs_core.py | 4 - .../libzfs_core/test/test_libzfs_core.py | 337 ----------------- 6 files changed, 2 insertions(+), 760 deletions(-) diff --git a/contrib/pyzfs/docs/source/index.rst b/contrib/pyzfs/docs/source/index.rst index 36c227a49..f63117c62 100644 --- a/contrib/pyzfs/docs/source/index.rst +++ b/contrib/pyzfs/docs/source/index.rst @@ -25,8 +25,7 @@ Documentation for the libzfs_core .. automodule:: libzfs_core :members: - :exclude-members: lzc_snap, lzc_recv, lzc_destroy_one, - lzc_inherit, lzc_set_props, lzc_list + :exclude-members: lzc_snap, lzc_recv, lzc_destroy_one Documentation for the libzfs_core exceptions ******************************************** diff --git a/contrib/pyzfs/libzfs_core/__init__.py b/contrib/pyzfs/libzfs_core/__init__.py index 13b50ca43..9165b178f 100644 --- a/contrib/pyzfs/libzfs_core/__init__.py +++ b/contrib/pyzfs/libzfs_core/__init__.py @@ -90,11 +90,6 @@ from ._libzfs_core import ( lzc_snap, lzc_rename, lzc_destroy, - lzc_inherit_prop, - lzc_get_props, - lzc_set_props, - lzc_list_children, - lzc_list_snaps, receive_header, ) @@ -146,11 +141,6 @@ __all__ = [ 'lzc_snap', 'lzc_rename', 'lzc_destroy', - 'lzc_inherit_prop', - 'lzc_get_props', - 'lzc_set_props', - 'lzc_list_children', - 'lzc_list_snaps', 'receive_header', ] diff --git a/contrib/pyzfs/libzfs_core/_error_translation.py b/contrib/pyzfs/libzfs_core/_error_translation.py index d5491a324..79a03b49f 100644 --- a/contrib/pyzfs/libzfs_core/_error_translation.py +++ b/contrib/pyzfs/libzfs_core/_error_translation.py @@ -638,64 +638,6 @@ def lzc_destroy_translate_error(ret, name): raise _generic_exception(ret, name, "Failed to destroy dataset") -def lzc_inherit_prop_translate_error(ret, name, prop): - if ret == 0: - return - if ret == errno.EINVAL: - _validate_fs_name(name) - raise lzc_exc.PropertyInvalid(prop) - if ret == errno.ENOENT: - raise lzc_exc.DatasetNotFound(name) - raise _generic_exception(ret, name, "Failed to inherit a property") - - -def lzc_set_prop_translate_error(ret, name, prop, val): - if ret == 0: - return - if ret == errno.EINVAL: - _validate_fs_or_snap_name(name) - raise lzc_exc.PropertyInvalid(prop) - if ret == errno.ENOENT: - raise lzc_exc.DatasetNotFound(name) - raise _generic_exception(ret, name, "Failed to set a property") - - -def lzc_get_props_translate_error(ret, name): - if ret == 0: - return - if ret == errno.EINVAL: - _validate_fs_or_snap_name(name) - if ret == errno.ENOENT: - raise lzc_exc.DatasetNotFound(name) - raise _generic_exception(ret, name, "Failed to get properties") - - -def lzc_list_children_translate_error(ret, name): - if ret == 0: - return - if ret == errno.EINVAL: - _validate_fs_name(name) - raise _generic_exception(ret, name, "Error while iterating children") - - -def lzc_list_snaps_translate_error(ret, name): - if ret == 0: - return - if ret == errno.EINVAL: - _validate_fs_name(name) - raise _generic_exception(ret, name, "Error while iterating snapshots") - - -def lzc_list_translate_error(ret, name, opts): - if ret == 0: - return - if ret == errno.ENOENT: - raise lzc_exc.DatasetNotFound(name) - if ret == errno.EINVAL: - _validate_fs_or_snap_name(name) - raise _generic_exception(ret, name, "Error obtaining a list") - - def _handle_err_list(ret, errlist, names, exception, mapper): ''' Convert one or more errors from an operation into the requested exception. diff --git a/contrib/pyzfs/libzfs_core/_libzfs_core.py b/contrib/pyzfs/libzfs_core/_libzfs_core.py index 0ebf99be6..113fde253 100644 --- a/contrib/pyzfs/libzfs_core/_libzfs_core.py +++ b/contrib/pyzfs/libzfs_core/_libzfs_core.py @@ -30,10 +30,7 @@ rather than by integer error codes. from __future__ import absolute_import, division, print_function import errno -import functools -import fcntl import os -import struct import threading from . import exceptions from . import _error_translation as errors @@ -47,46 +44,10 @@ from ._constants import ( # noqa: F401 zfs_keyformat, zio_encrypt ) -from .ctypes import ( - int32_t, - uint64_t -) +from .ctypes import uint64_t from ._nvlist import nvlist_in, nvlist_out -def _uncommitted(depends_on=None): - ''' - Mark an API function as being an uncommitted extension that might not be - available. - - :param function depends_on: the function that would be checked instead of - a decorated function. For example, if the decorated function uses - another uncommitted function. - - This decorator transforms a decorated function to raise - :exc:`NotImplementedError` if the C libzfs_core library does not provide - a function with the same name as the decorated function. - - The optional `depends_on` parameter can be provided if the decorated - function does not directly call the C function but instead calls another - Python function that follows the typical convention. - One example is :func:`lzc_list_snaps` that calls :func:`lzc_list` that - calls ``lzc_list`` in libzfs_core. - - This decorator is implemented using :func:`is_supported`. - ''' - def _uncommitted_decorator(func, depends_on=depends_on): - @functools.wraps(func) - def _f(*args, **kwargs): - if not is_supported(_f): - raise NotImplementedError(func.__name__) - return func(*args, **kwargs) - if depends_on is not None: - _f._check_func = depends_on - return _f - return _uncommitted_decorator - - def lzc_create(name, ds_type='zfs', props=None, key=None): ''' Create a ZFS filesystem or a ZFS volume ("zvol"). @@ -827,7 +788,6 @@ def lzc_exists(name): return bool(ret) -@_uncommitted() def lzc_change_key(fsname, crypt_cmd, props=None, key=None): ''' Change encryption key on the specified dataset. @@ -868,7 +828,6 @@ def lzc_change_key(fsname, crypt_cmd, props=None, key=None): errors.lzc_change_key_translate_error(ret, fsname) -@_uncommitted() def lzc_load_key(fsname, noop, key): ''' Load or verify encryption key on the specified dataset. @@ -888,7 +847,6 @@ def lzc_load_key(fsname, noop, key): errors.lzc_load_key_translate_error(ret, fsname, noop) -@_uncommitted() def lzc_unload_key(fsname): ''' Unload encryption key from the specified dataset. @@ -1200,7 +1158,6 @@ def receive_header(fd): return (header, record) -@_uncommitted() def lzc_receive_one( snapname, fd, begin_record, force=False, resumable=False, raw=False, origin=None, props=None, cleanup_fd=-1, action_handle=0 @@ -1306,7 +1263,6 @@ def lzc_receive_one( return (int(c_read_bytes[0]), action_handle) -@_uncommitted() def lzc_receive_with_cmdprops( snapname, fd, begin_record, force=False, resumable=False, raw=False, origin=None, props=None, cmdprops=None, key=None, cleanup_fd=-1, @@ -1427,7 +1383,6 @@ def lzc_receive_with_cmdprops( return (int(c_read_bytes[0]), action_handle) -@_uncommitted() def lzc_receive_with_heal( snapname, fd, begin_record, force=False, corrective=True, resumable=False, raw=False, origin=None, props=None, cmdprops=None, key=None, cleanup_fd=-1, @@ -1556,7 +1511,6 @@ def lzc_receive_with_heal( return (int(c_read_bytes[0]), action_handle) -@_uncommitted() def lzc_reopen(poolname, restart=True): ''' Reopen a pool @@ -1627,7 +1581,6 @@ def lzc_send_resume( errors.lzc_send_translate_error(ret, snapname, fromsnap, fd, flags) -@_uncommitted() def lzc_sync(poolname, force=False): ''' Forces all in-core dirty data to be written to the primary pool storage @@ -1674,7 +1627,6 @@ def is_supported(func): return getattr(_lib, fname, None) is not None -@_uncommitted() def lzc_promote(name): ''' Promotes the ZFS dataset. @@ -1693,7 +1645,6 @@ def lzc_promote(name): errors.lzc_promote_translate_error(ret, name) -@_uncommitted() def lzc_pool_checkpoint(name): ''' Creates a checkpoint for the specified pool. @@ -1710,7 +1661,6 @@ def 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. @@ -1758,304 +1708,6 @@ def lzc_destroy(name): errors.lzc_destroy_translate_error(ret, name) -@_uncommitted() -def lzc_inherit(name, prop): - ''' - Inherit properties from a parent dataset of the given ZFS dataset. - - :param bytes name: the name of the dataset. - :param bytes prop: the name of the property to inherit. - :raises NameInvalid: if the dataset name is invalid. - :raises NameTooLong: if the dataset name is too long. - :raises DatasetNotFound: if the dataset does not exist. - :raises PropertyInvalid: if one or more of the specified properties is - invalid or has an invalid type or value. - - Inheriting a property actually resets it to its default value - or removes it if it's a user property, so that the property could be - inherited if it's inheritable. If the property is not inheritable - then it would just have its default value. - - This function can be used on snapshots to inherit user defined properties. - ''' - ret = _lib.lzc_inherit(name, prop, _ffi.NULL) - errors.lzc_inherit_prop_translate_error(ret, name, prop) - - -# As the extended API is not committed yet, the names of the new interfaces -# are not settled down yet. -# lzc_inherit_prop makes it clearer what is to be inherited. -lzc_inherit_prop = lzc_inherit - - -@_uncommitted() -def lzc_set_props(name, prop, val): - ''' - Set properties of the ZFS dataset. - - :param bytes name: the name of the dataset. - :param bytes prop: the name of the property. - :param Any val: the value of the property. - :raises NameInvalid: if the dataset name is invalid. - :raises NameTooLong: if the dataset name is too long. - :raises DatasetNotFound: if the dataset does not exist. - :raises NoSpace: if the property controls a quota and the values is too - small for that quota. - :raises PropertyInvalid: if one or more of the specified properties is - invalid or has an invalid type or value. - - This function can be used on snapshots to set user defined properties. - - .. note:: - An attempt to set a readonly / statistic property is ignored - without reporting any error. - ''' - props = {prop: val} - props_nv = nvlist_in(props) - ret = _lib.lzc_set_props(name, props_nv, _ffi.NULL, _ffi.NULL) - errors.lzc_set_prop_translate_error(ret, name, prop, val) - - -# As the extended API is not committed yet, the names of the new interfaces -# are not settled down yet. -# It's not clear if atomically setting multiple properties is an achievable -# goal and an interface acting on multiple entities must do so atomically -# by convention. -# Being able to set a single property at a time is sufficient for ClusterHQ. -lzc_set_prop = lzc_set_props - - -@_uncommitted() -def lzc_list(name, options): - ''' - List subordinate elements of the given dataset. - - This function can be used to list child datasets and snapshots of the given - dataset. The listed elements can be filtered by their type and by their - depth relative to the starting dataset. - - :param bytes name: the name of the dataset to be listed, could be a - snapshot or a dataset. - :param options: a `dict` of the options that control the listing behavior. - :type options: dict of bytes:Any - :return: a pair of file descriptors the first of which can be used to read - the listing. - :rtype: tuple of (int, int) - :raises DatasetNotFound: if the dataset does not exist. - - Two options are currently available: - - recurse : integer or None - specifies depth of the recursive listing. If ``None`` the depth is not - limited. - Absence of this option means that only the given dataset is listed. - - type : dict of bytes:None - specifies dataset types to include into the listing. - Currently allowed keys are "filesystem", "volume", "snapshot". - Absence of this option implies all types. - - The first of the returned file descriptors can be used to - read the listing in a binary encoded format. The data is - a series of variable sized records each starting with a fixed - size header, the header is followed by a serialized ``nvlist``. - Each record describes a single element and contains the element's - name as well as its properties. - The file descriptor must be closed after reading from it. - - The second file descriptor represents a pipe end to which the - kernel driver is writing information. It should not be closed - until all interesting information has been read and it must - be explicitly closed afterwards. - ''' - (rfd, wfd) = os.pipe() - fcntl.fcntl(rfd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) - fcntl.fcntl(wfd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) - options = options.copy() - options['fd'] = int32_t(wfd) - opts_nv = nvlist_in(options) - ret = _lib.lzc_list(name, opts_nv) - if ret == errno.ESRCH: - return (None, None) - errors.lzc_list_translate_error(ret, name, options) - return (rfd, wfd) - - -# Description of the binary format used to pass data from the kernel. -_PIPE_RECORD_FORMAT = 'IBBBB' -_PIPE_RECORD_SIZE = struct.calcsize(_PIPE_RECORD_FORMAT) - - -def _list(name, recurse=None, types=None): - ''' - A wrapper for :func:`lzc_list` that hides details of working - with the file descriptors and provides data in an easy to - consume format. - - :param bytes name: the name of the dataset to be listed, could be a - snapshot, a volume or a filesystem. - :param recurse: specifies depth of the recursive listing. If ``None`` the - depth is not limited. - :param types: specifies dataset types to include into the listing. - Currently allowed keys are "filesystem", "volume", "snapshot". ``None`` - is equivalent to specifying the type of the dataset named by `name`. - :type types: list of bytes or None - :type recurse: integer or None - :return: a list of dictionaries each describing a single listed element. - :rtype: list of dict - ''' - options = {} - - # Convert types to a dict suitable for mapping to an nvlist. - if types is not None: - types = {x: None for x in types} - options['type'] = types - if recurse is None or recurse > 0: - options['recurse'] = recurse - - # Note that other_fd is used by the kernel side to write - # the data, so we have to keep that descriptor open until - # we are done. - # Also, we have to explicitly close the descriptor as the - # kernel doesn't do that. - (fd, other_fd) = lzc_list(name, options) - if fd is None: - return - - try: - while True: - record_bytes = os.read(fd, _PIPE_RECORD_SIZE) - if not record_bytes: - break - (size, _, err, _, _) = struct.unpack( - _PIPE_RECORD_FORMAT, record_bytes) - if err == errno.ESRCH: - break - errors.lzc_list_translate_error(err, name, options) - if size == 0: - break - data_bytes = os.read(fd, size) - result = {} - with nvlist_out(result) as nvp: - ret = _lib.nvlist_unpack(data_bytes, size, nvp, 0) - if ret != 0: - raise exceptions.ZFSGenericError( - ret, None, "Failed to unpack list data") - yield result - finally: - os.close(other_fd) - os.close(fd) - - -@_uncommitted(lzc_list) -def lzc_get_props(name): - ''' - Get properties of the ZFS dataset. - - :param bytes name: the name of the dataset. - :raises DatasetNotFound: if the dataset does not exist. - :raises NameInvalid: if the dataset name is invalid. - :raises NameTooLong: if the dataset name is too long. - :return: a dictionary mapping the property names to their values. - :rtype: dict of bytes:Any - - .. note:: - The value of ``clones`` property is a `list` of clone names as byte - strings. - - .. warning:: - The returned dictionary does not contain entries for properties - with default values. One exception is the ``mountpoint`` property - for which the default value is derived from the dataset name. - ''' - result = next(_list(name, recurse=0)) - is_snapshot = result['dmu_objset_stats']['dds_is_snapshot'] - result = result['properties'] - # In most cases the source of the property is uninteresting and the - # value alone is sufficient. One exception is the 'mountpoint' - # property the final value of which is not the same as the inherited - # value. - mountpoint = result.get('mountpoint') - if mountpoint is not None: - mountpoint_src = mountpoint['source'] - mountpoint_val = mountpoint['value'] - # 'source' is the name of the dataset that has 'mountpoint' set - # to a non-default value and from which the current dataset inherits - # the property. 'source' can be the current dataset if its - # 'mountpoint' is explicitly set. - # 'source' can also be a special value like '$recvd', that case - # is equivalent to the property being set on the current dataset. - # Note that a normal mountpoint value should start with '/' - # unlike the special values "none" and "legacy". - if (mountpoint_val.startswith('/') and - not mountpoint_src.startswith('$')): - mountpoint_val = mountpoint_val + name[len(mountpoint_src):] - elif not is_snapshot: - mountpoint_val = '/' + name - else: - mountpoint_val = None - result = {k: result[k]['value'] for k in result} - if 'clones' in result: - result['clones'] = list(result['clones'].keys()) - if mountpoint_val is not None: - result['mountpoint'] = mountpoint_val - return result - - -@_uncommitted(lzc_list) -def lzc_list_children(name): - ''' - List the children of the ZFS dataset. - - :param bytes name: the name of the dataset. - :return: an iterator that produces the names of the children. - :raises NameInvalid: if the dataset name is invalid. - :raises NameTooLong: if the dataset name is too long. - :raises DatasetNotFound: if the dataset does not exist. - - .. warning:: - If the dataset does not exist, then the returned iterator would produce - no results and no error is reported. - That case is indistinguishable from the dataset having no children. - - An attempt to list children of a snapshot is silently ignored as well. - ''' - children = [] - for entry in _list(name, recurse=1, types=['filesystem', 'volume']): - child = entry['name'] - if child != name: - children.append(child) - - return iter(children) - - -@_uncommitted(lzc_list) -def lzc_list_snaps(name): - ''' - List the snapshots of the ZFS dataset. - - :param bytes name: the name of the dataset. - :return: an iterator that produces the names of the snapshots. - :raises NameInvalid: if the dataset name is invalid. - :raises NameTooLong: if the dataset name is too long. - :raises DatasetNotFound: if the dataset does not exist. - - .. warning:: - If the dataset does not exist, then the returned iterator would produce - no results and no error is reported. - That case is indistinguishable from the dataset having no snapshots. - - An attempt to list snapshots of a snapshot is silently ignored as well. - ''' - snaps = [] - for entry in _list(name, recurse=1, types=['snapshot']): - snap = entry['name'] - if snap != name: - snaps.append(snap) - - return iter(snaps) - - # TODO: a better way to init and uninit the library def _initialize(): class LazyInit(object): diff --git a/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py b/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py index ca752f654..8db9ff3a2 100644 --- a/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py +++ b/contrib/pyzfs/libzfs_core/bindings/libzfs_core.py @@ -136,10 +136,6 @@ CDEF = """ int lzc_pool_checkpoint_discard(const char *); int lzc_rename(const char *, const char *); int lzc_destroy(const char *fsname); - - int lzc_inherit(const char *fsname, const char *name, nvlist_t *); - int lzc_set_props(const char *, nvlist_t *, nvlist_t *, nvlist_t *); - int lzc_list (const char *, nvlist_t *); """ SOURCE = """ diff --git a/contrib/pyzfs/libzfs_core/test/test_libzfs_core.py b/contrib/pyzfs/libzfs_core/test/test_libzfs_core.py index bad1af2d1..0587a5b77 100644 --- a/contrib/pyzfs/libzfs_core/test/test_libzfs_core.py +++ b/contrib/pyzfs/libzfs_core/test/test_libzfs_core.py @@ -3727,182 +3727,6 @@ zfs.sync.snapshot('""" + pool + b"""@zcp') 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(b"fs1/fs") - names = [ZFSTest.pool.makeName(b"fs1/fs/test1"), - ZFSTest.pool.makeName(b"fs1/fs/test2"), - ZFSTest.pool.makeName(b"fs1/fs/test3"), ] - # and one snap to see that it is not listed - snap = ZFSTest.pool.makeName(b"fs1/fs@test") - - for fs in names: - lzc.lzc_create(fs) - lzc.lzc_snapshot([snap]) - - children = list(lzc.lzc_list_children(name)) - self.assertItemsEqual(children, names) - - @needs_support(lzc.lzc_list_children) - def test_list_children_nonexistent(self): - fs = ZFSTest.pool.makeName(b"nonexistent") - - with self.assertRaises(lzc_exc.DatasetNotFound): - list(lzc.lzc_list_children(fs)) - - @needs_support(lzc.lzc_list_children) - def test_list_children_of_snap(self): - snap = ZFSTest.pool.makeName(b"@newsnap") - - lzc.lzc_snapshot([snap]) - children = list(lzc.lzc_list_children(snap)) - self.assertEqual(children, []) - - @needs_support(lzc.lzc_list_snaps) - def test_list_snaps(self): - name = ZFSTest.pool.makeName(b"fs1/fs") - names = [ZFSTest.pool.makeName(b"fs1/fs@test1"), - ZFSTest.pool.makeName(b"fs1/fs@test2"), - ZFSTest.pool.makeName(b"fs1/fs@test3"), ] - # and one filesystem to see that it is not listed - fs = ZFSTest.pool.makeName(b"fs1/fs/test") - - for snap in names: - lzc.lzc_snapshot([snap]) - lzc.lzc_create(fs) - - snaps = list(lzc.lzc_list_snaps(name)) - self.assertItemsEqual(snaps, names) - - @needs_support(lzc.lzc_list_snaps) - def test_list_snaps_nonexistent(self): - fs = ZFSTest.pool.makeName(b"nonexistent") - - with self.assertRaises(lzc_exc.DatasetNotFound): - list(lzc.lzc_list_snaps(fs)) - - @needs_support(lzc.lzc_list_snaps) - def test_list_snaps_of_snap(self): - snap = ZFSTest.pool.makeName(b"@newsnap") - - lzc.lzc_snapshot([snap]) - snaps = list(lzc.lzc_list_snaps(snap)) - self.assertEqual(snaps, []) - - @needs_support(lzc.lzc_get_props) - def test_get_fs_props(self): - fs = ZFSTest.pool.makeName(b"new") - props = {b"user:foo": b"bar"} - - lzc.lzc_create(fs, props=props) - actual_props = lzc.lzc_get_props(fs) - self.assertDictContainsSubset(props, actual_props) - - @needs_support(lzc.lzc_get_props) - def test_get_fs_props_with_child(self): - parent = ZFSTest.pool.makeName(b"parent") - child = ZFSTest.pool.makeName(b"parent/child") - parent_props = {b"user:foo": b"parent"} - child_props = {b"user:foo": b"child"} - - lzc.lzc_create(parent, props=parent_props) - lzc.lzc_create(child, props=child_props) - actual_parent_props = lzc.lzc_get_props(parent) - actual_child_props = lzc.lzc_get_props(child) - self.assertDictContainsSubset(parent_props, actual_parent_props) - self.assertDictContainsSubset(child_props, actual_child_props) - - @needs_support(lzc.lzc_get_props) - def test_get_snap_props(self): - snapname = ZFSTest.pool.makeName(b"@snap") - snaps = [snapname] - props = {b"user:foo": b"bar"} - - lzc.lzc_snapshot(snaps, props) - actual_props = lzc.lzc_get_props(snapname) - self.assertDictContainsSubset(props, actual_props) - - @needs_support(lzc.lzc_get_props) - def test_get_props_nonexistent(self): - fs = ZFSTest.pool.makeName(b"nonexistent") - - with self.assertRaises(lzc_exc.DatasetNotFound): - lzc.lzc_get_props(fs) - - @needs_support(lzc.lzc_get_props) - def test_get_mountpoint_none(self): - ''' - If the *mountpoint* property is set to none, then its - value is returned as `bytes` "none". - Also, a child filesystem inherits that value. - ''' - fs = ZFSTest.pool.makeName(b"new") - child = ZFSTest.pool.makeName(b"new/child") - props = {b"mountpoint": b"none"} - - lzc.lzc_create(fs, props=props) - lzc.lzc_create(child) - actual_props = lzc.lzc_get_props(fs) - self.assertDictContainsSubset(props, actual_props) - # check that mountpoint value is correctly inherited - child_props = lzc.lzc_get_props(child) - self.assertDictContainsSubset(props, child_props) - - @needs_support(lzc.lzc_get_props) - def test_get_mountpoint_legacy(self): - ''' - If the *mountpoint* property is set to legacy, then its - value is returned as `bytes` "legacy". - Also, a child filesystem inherits that value. - ''' - fs = ZFSTest.pool.makeName(b"new") - child = ZFSTest.pool.makeName(b"new/child") - props = {b"mountpoint": b"legacy"} - - lzc.lzc_create(fs, props=props) - lzc.lzc_create(child) - actual_props = lzc.lzc_get_props(fs) - self.assertDictContainsSubset(props, actual_props) - # check that mountpoint value is correctly inherited - child_props = lzc.lzc_get_props(child) - self.assertDictContainsSubset(props, child_props) - - @needs_support(lzc.lzc_get_props) - def test_get_mountpoint_path(self): - ''' - If the *mountpoint* property is set to a path and the property - is not explicitly set on a child filesystem, then its - value is that of the parent filesystem with the child's - name appended using the '/' separator. - ''' - fs = ZFSTest.pool.makeName(b"new") - child = ZFSTest.pool.makeName(b"new/child") - props = {b"mountpoint": b"/mnt"} - - lzc.lzc_create(fs, props=props) - lzc.lzc_create(child) - actual_props = lzc.lzc_get_props(fs) - self.assertDictContainsSubset(props, actual_props) - # check that mountpoint value is correctly inherited - child_props = lzc.lzc_get_props(child) - self.assertDictContainsSubset( - {b"mountpoint": b"/mnt/child"}, child_props) - - @needs_support(lzc.lzc_get_props) - def test_get_snap_clones(self): - fs = ZFSTest.pool.makeName(b"new") - snap = ZFSTest.pool.makeName(b"@snap") - clone1 = ZFSTest.pool.makeName(b"clone1") - clone2 = ZFSTest.pool.makeName(b"clone2") - - lzc.lzc_create(fs) - lzc.lzc_snapshot([snap]) - lzc.lzc_clone(clone1, snap) - lzc.lzc_clone(clone2, snap) - - clones_prop = lzc.lzc_get_props(snap)["clones"] - self.assertItemsEqual(clones_prop, [clone1, clone2]) - @needs_support(lzc.lzc_rename) def test_rename(self): src = ZFSTest.pool.makeName(b"source") @@ -3967,167 +3791,6 @@ zfs.sync.snapshot('""" + pool + b"""@zcp') with self.assertRaises(lzc_exc.FilesystemNotFound): lzc.lzc_destroy(fs) - @needs_support(lzc.lzc_inherit_prop) - def test_inherit_prop(self): - parent = ZFSTest.pool.makeName(b"parent") - child = ZFSTest.pool.makeName(b"parent/child") - the_prop = b"user:foo" - parent_props = {the_prop: b"parent"} - child_props = {the_prop: b"child"} - - lzc.lzc_create(parent, props=parent_props) - lzc.lzc_create(child, props=child_props) - lzc.lzc_inherit_prop(child, the_prop) - actual_props = lzc.lzc_get_props(child) - self.assertDictContainsSubset(parent_props, actual_props) - - @needs_support(lzc.lzc_inherit_prop) - def test_inherit_missing_prop(self): - parent = ZFSTest.pool.makeName(b"parent") - child = ZFSTest.pool.makeName(b"parent/child") - the_prop = "user:foo" - child_props = {the_prop: "child"} - - lzc.lzc_create(parent) - lzc.lzc_create(child, props=child_props) - lzc.lzc_inherit_prop(child, the_prop) - actual_props = lzc.lzc_get_props(child) - self.assertNotIn(the_prop, actual_props) - - @needs_support(lzc.lzc_inherit_prop) - def test_inherit_readonly_prop(self): - parent = ZFSTest.pool.makeName(b"parent") - child = ZFSTest.pool.makeName(b"parent/child") - the_prop = b"createtxg" - - lzc.lzc_create(parent) - lzc.lzc_create(child) - with self.assertRaises(lzc_exc.PropertyInvalid): - lzc.lzc_inherit_prop(child, the_prop) - - @needs_support(lzc.lzc_inherit_prop) - def test_inherit_unknown_prop(self): - parent = ZFSTest.pool.makeName(b"parent") - child = ZFSTest.pool.makeName(b"parent/child") - the_prop = b"nosuchprop" - - lzc.lzc_create(parent) - lzc.lzc_create(child) - with self.assertRaises(lzc_exc.PropertyInvalid): - lzc.lzc_inherit_prop(child, the_prop) - - @needs_support(lzc.lzc_inherit_prop) - def test_inherit_prop_on_snap(self): - fs = ZFSTest.pool.makeName(b"new") - snapname = ZFSTest.pool.makeName(b"new@snap") - prop = b"user:foo" - fs_val = b"fs" - snap_val = b"snap" - - lzc.lzc_create(fs, props={prop: fs_val}) - lzc.lzc_snapshot([snapname], props={prop: snap_val}) - - actual_props = lzc.lzc_get_props(snapname) - self.assertDictContainsSubset({prop: snap_val}, actual_props) - - lzc.lzc_inherit_prop(snapname, prop) - actual_props = lzc.lzc_get_props(snapname) - self.assertDictContainsSubset({prop: fs_val}, actual_props) - - @needs_support(lzc.lzc_set_prop) - def test_set_fs_prop(self): - fs = ZFSTest.pool.makeName(b"new") - prop = b"user:foo" - val = b"bar" - - lzc.lzc_create(fs) - lzc.lzc_set_prop(fs, prop, val) - actual_props = lzc.lzc_get_props(fs) - self.assertDictContainsSubset({prop: val}, actual_props) - - @needs_support(lzc.lzc_set_prop) - def test_set_snap_prop(self): - snapname = ZFSTest.pool.makeName(b"@snap") - prop = b"user:foo" - val = b"bar" - - lzc.lzc_snapshot([snapname]) - lzc.lzc_set_prop(snapname, prop, val) - actual_props = lzc.lzc_get_props(snapname) - self.assertDictContainsSubset({prop: val}, actual_props) - - @needs_support(lzc.lzc_set_prop) - def test_set_prop_nonexistent(self): - fs = ZFSTest.pool.makeName(b"nonexistent") - prop = b"user:foo" - val = b"bar" - - with self.assertRaises(lzc_exc.DatasetNotFound): - lzc.lzc_set_prop(fs, prop, val) - - @needs_support(lzc.lzc_set_prop) - def test_set_sys_prop(self): - fs = ZFSTest.pool.makeName(b"new") - prop = b"recordsize" - val = 4096 - - lzc.lzc_create(fs) - lzc.lzc_set_prop(fs, prop, val) - actual_props = lzc.lzc_get_props(fs) - self.assertDictContainsSubset({prop: val}, actual_props) - - @needs_support(lzc.lzc_set_prop) - def test_set_invalid_prop(self): - fs = ZFSTest.pool.makeName(b"new") - prop = b"nosuchprop" - val = 0 - - lzc.lzc_create(fs) - with self.assertRaises(lzc_exc.PropertyInvalid): - lzc.lzc_set_prop(fs, prop, val) - - @needs_support(lzc.lzc_set_prop) - def test_set_invalid_value_prop(self): - fs = ZFSTest.pool.makeName(b"new") - prop = b"atime" - val = 100 - - lzc.lzc_create(fs) - with self.assertRaises(lzc_exc.PropertyInvalid): - lzc.lzc_set_prop(fs, prop, val) - - @needs_support(lzc.lzc_set_prop) - def test_set_invalid_value_prop_2(self): - fs = ZFSTest.pool.makeName(b"new") - prop = b"readonly" - val = 100 - - lzc.lzc_create(fs) - with self.assertRaises(lzc_exc.PropertyInvalid): - lzc.lzc_set_prop(fs, prop, val) - - @needs_support(lzc.lzc_set_prop) - def test_set_prop_too_small_quota(self): - fs = ZFSTest.pool.makeName(b"new") - prop = b"refquota" - val = 1 - - lzc.lzc_create(fs) - with self.assertRaises(lzc_exc.NoSpace): - lzc.lzc_set_prop(fs, prop, val) - - @needs_support(lzc.lzc_set_prop) - def test_set_readonly_prop(self): - fs = ZFSTest.pool.makeName(b"new") - prop = b"creation" - val = 0 - - lzc.lzc_create(fs) - lzc.lzc_set_prop(fs, prop, val) - actual_props = lzc.lzc_get_props(fs) - # the change is silently ignored - self.assertTrue(actual_props[prop] != val) - class _TempPool(object): SNAPSHOTS = [b'snap', b'snap1', b'snap2']