4371 lines
158 KiB
Python
4371 lines
158 KiB
Python
|
#
|
||
|
# Copyright 2015 ClusterHQ
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
#
|
||
|
|
||
|
"""
|
||
|
Tests for `libzfs_core` operations.
|
||
|
|
||
|
These are mostly functional and conformance tests that validate
|
||
|
that the operations produce expected effects or fail with expected
|
||
|
exceptions.
|
||
|
"""
|
||
|
from __future__ import absolute_import, division, print_function
|
||
|
|
||
|
import unittest
|
||
|
import contextlib
|
||
|
import errno
|
||
|
import filecmp
|
||
|
import os
|
||
|
import platform
|
||
|
import resource
|
||
|
import shutil
|
||
|
import stat
|
||
|
import subprocess
|
||
|
import sys
|
||
|
import tempfile
|
||
|
import time
|
||
|
import uuid
|
||
|
import itertools
|
||
|
import zlib
|
||
|
from .. import _libzfs_core as lzc
|
||
|
from .. import exceptions as lzc_exc
|
||
|
from .._nvlist import packed_nvlist_out
|
||
|
|
||
|
|
||
|
def _print(*args):
|
||
|
for arg in args:
|
||
|
print(arg, end=' ')
|
||
|
print()
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def suppress(exceptions=None):
|
||
|
try:
|
||
|
yield
|
||
|
except BaseException as e:
|
||
|
if exceptions is None or isinstance(e, exceptions):
|
||
|
pass
|
||
|
else:
|
||
|
raise
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def _zfs_mount(fs):
|
||
|
mntdir = tempfile.mkdtemp()
|
||
|
if platform.system() == 'SunOS':
|
||
|
mount_cmd = ['mount', '-F', 'zfs', fs, mntdir]
|
||
|
else:
|
||
|
mount_cmd = ['mount', '-t', 'zfs', fs, mntdir]
|
||
|
unmount_cmd = ['umount', '-f', mntdir]
|
||
|
|
||
|
try:
|
||
|
subprocess.check_output(mount_cmd, stderr=subprocess.STDOUT)
|
||
|
try:
|
||
|
yield mntdir
|
||
|
finally:
|
||
|
with suppress():
|
||
|
subprocess.check_output(unmount_cmd, stderr=subprocess.STDOUT)
|
||
|
except subprocess.CalledProcessError as e:
|
||
|
print('failed to mount %s @ %s : %s' % (fs, mntdir, e.output))
|
||
|
raise
|
||
|
finally:
|
||
|
os.rmdir(mntdir)
|
||
|
|
||
|
|
||
|
# XXX On illumos it is impossible to explicitly mount a snapshot.
|
||
|
# So, either we need to implicitly mount it using .zfs/snapshot/
|
||
|
# or we need to create a clone and mount it readonly (and discard
|
||
|
# it afterwards).
|
||
|
# At the moment the former approach is implemented.
|
||
|
|
||
|
# This dictionary is used to keep track of mounted filesystems
|
||
|
# (not snapshots), so that we do not try to mount a filesystem
|
||
|
# more than once in the case more than one snapshot of the
|
||
|
# filesystem is accessed from the same context or the filesystem
|
||
|
# and its snapshot are accessed.
|
||
|
_mnttab = {}
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def _illumos_mount_fs(fs):
|
||
|
if fs in _mnttab:
|
||
|
yield _mnttab[fs]
|
||
|
else:
|
||
|
with _zfs_mount(fs) as mntdir:
|
||
|
_mnttab[fs] = mntdir
|
||
|
try:
|
||
|
yield mntdir
|
||
|
finally:
|
||
|
_mnttab.pop(fs, None)
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def _illumos_mount_snap(fs):
|
||
|
(base, snap) = fs.split('@', 1)
|
||
|
with _illumos_mount_fs(base) as mntdir:
|
||
|
yield os.path.join(mntdir, '.zfs', 'snapshot', snap)
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def _zfs_mount_illumos(fs):
|
||
|
if '@' not in fs:
|
||
|
with _illumos_mount_fs(fs) as mntdir:
|
||
|
yield mntdir
|
||
|
else:
|
||
|
with _illumos_mount_snap(fs) as mntdir:
|
||
|
yield mntdir
|
||
|
|
||
|
|
||
|
if platform.system() == 'SunOS':
|
||
|
zfs_mount = _zfs_mount_illumos
|
||
|
else:
|
||
|
zfs_mount = _zfs_mount
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def cleanup_fd():
|
||
|
fd = os.open('/dev/zfs', os.O_EXCL)
|
||
|
try:
|
||
|
yield fd
|
||
|
finally:
|
||
|
os.close(fd)
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def os_open(name, mode):
|
||
|
fd = os.open(name, mode)
|
||
|
try:
|
||
|
yield fd
|
||
|
finally:
|
||
|
os.close(fd)
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def dev_null():
|
||
|
with os_open('/dev/null', os.O_WRONLY) as fd:
|
||
|
yield fd
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def dev_zero():
|
||
|
with os_open('/dev/zero', os.O_RDONLY) as fd:
|
||
|
yield fd
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def temp_file_in_fs(fs):
|
||
|
with zfs_mount(fs) as mntdir:
|
||
|
with tempfile.NamedTemporaryFile(dir=mntdir) as f:
|
||
|
for i in range(1024):
|
||
|
f.write(b'x' * 1024)
|
||
|
f.flush()
|
||
|
yield f.name
|
||
|
|
||
|
|
||
|
def make_snapshots(fs, before, modified, after):
|
||
|
def _maybe_snap(snap):
|
||
|
if snap is not None:
|
||
|
if not snap.startswith(fs):
|
||
|
snap = fs + b'@' + snap
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
return snap
|
||
|
|
||
|
before = _maybe_snap(before)
|
||
|
with temp_file_in_fs(fs) as name:
|
||
|
modified = _maybe_snap(modified)
|
||
|
after = _maybe_snap(after)
|
||
|
|
||
|
return (name, (before, modified, after))
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def streams(fs, first, second):
|
||
|
(filename, snaps) = make_snapshots(fs, None, first, second)
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as full:
|
||
|
lzc.lzc_send(snaps[1], None, full.fileno())
|
||
|
full.seek(0)
|
||
|
if snaps[2] is not None:
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as incremental:
|
||
|
lzc.lzc_send(snaps[2], snaps[1], incremental.fileno())
|
||
|
incremental.seek(0)
|
||
|
yield (filename, (full, incremental))
|
||
|
else:
|
||
|
yield (filename, (full, None))
|
||
|
|
||
|
|
||
|
@contextlib.contextmanager
|
||
|
def encrypted_filesystem():
|
||
|
fs = ZFSTest.pool.getFilesystem(b"encrypted")
|
||
|
name = fs.getName()
|
||
|
filename = None
|
||
|
key = os.urandom(lzc.WRAPPING_KEY_LEN)
|
||
|
with tempfile.NamedTemporaryFile() as f:
|
||
|
filename = "file://" + f.name
|
||
|
props = {
|
||
|
b"encryption": lzc.zio_encrypt.ZIO_CRYPT_AES_256_CCM,
|
||
|
b"keylocation": filename.encode(),
|
||
|
b"keyformat": lzc.zfs_keyformat.ZFS_KEYFORMAT_RAW,
|
||
|
}
|
||
|
lzc.lzc_create(name, 'zfs', props=props, key=key)
|
||
|
yield (name, key)
|
||
|
|
||
|
|
||
|
def runtimeSkipIf(check_method, message):
|
||
|
def _decorator(f):
|
||
|
def _f(_self, *args, **kwargs):
|
||
|
if check_method(_self):
|
||
|
return _self.skipTest(message)
|
||
|
else:
|
||
|
return f(_self, *args, **kwargs)
|
||
|
_f.__name__ = f.__name__
|
||
|
return _f
|
||
|
return _decorator
|
||
|
|
||
|
|
||
|
def skipIfFeatureAvailable(feature, message):
|
||
|
return runtimeSkipIf(
|
||
|
lambda _self: _self.__class__.pool.isPoolFeatureAvailable(feature),
|
||
|
message)
|
||
|
|
||
|
|
||
|
def skipUnlessFeatureEnabled(feature, message):
|
||
|
return runtimeSkipIf(
|
||
|
lambda _self: not _self.__class__.pool.isPoolFeatureEnabled(feature),
|
||
|
message)
|
||
|
|
||
|
|
||
|
def skipUnlessBookmarksSupported(f):
|
||
|
return skipUnlessFeatureEnabled(
|
||
|
'bookmarks', 'bookmarks are not enabled')(f)
|
||
|
|
||
|
|
||
|
def snap_always_unmounted_before_destruction():
|
||
|
# Apparently ZoL automatically unmounts the snapshot
|
||
|
# only if it is mounted at its default .zfs/snapshot
|
||
|
# mountpoint.
|
||
|
return (
|
||
|
platform.system() != 'Linux', 'snapshot is not auto-unmounted')
|
||
|
|
||
|
|
||
|
def illumos_bug_6379():
|
||
|
# zfs_ioc_hold() panics on a bad cleanup fd
|
||
|
return (
|
||
|
platform.system() == 'SunOS',
|
||
|
'see https://www.illumos.org/issues/6379')
|
||
|
|
||
|
|
||
|
def needs_support(function):
|
||
|
return unittest.skipUnless(
|
||
|
lzc.is_supported(function),
|
||
|
'{} not available'.format(function.__name__))
|
||
|
|
||
|
|
||
|
class ZFSTest(unittest.TestCase):
|
||
|
POOL_FILE_SIZE = 128 * 1024 * 1024
|
||
|
FILESYSTEMS = [b'fs1', b'fs2', b'fs1/fs']
|
||
|
|
||
|
pool = None
|
||
|
misc_pool = None
|
||
|
readonly_pool = None
|
||
|
|
||
|
@classmethod
|
||
|
def setUpClass(cls):
|
||
|
try:
|
||
|
cls.pool = _TempPool(filesystems=cls.FILESYSTEMS)
|
||
|
cls.misc_pool = _TempPool()
|
||
|
cls.readonly_pool = _TempPool(
|
||
|
filesystems=cls.FILESYSTEMS, readonly=True)
|
||
|
cls.pools = [cls.pool, cls.misc_pool, cls.readonly_pool]
|
||
|
except Exception:
|
||
|
cls._cleanUp()
|
||
|
raise
|
||
|
|
||
|
@classmethod
|
||
|
def tearDownClass(cls):
|
||
|
cls._cleanUp()
|
||
|
|
||
|
@classmethod
|
||
|
def _cleanUp(cls):
|
||
|
for pool in [cls.pool, cls.misc_pool, cls.readonly_pool]:
|
||
|
if pool is not None:
|
||
|
pool.cleanUp()
|
||
|
|
||
|
def setUp(self):
|
||
|
pass
|
||
|
|
||
|
def tearDown(self):
|
||
|
for pool in ZFSTest.pools:
|
||
|
pool.reset()
|
||
|
|
||
|
def assertExists(self, name):
|
||
|
self.assertTrue(
|
||
|
lzc.lzc_exists(name), 'ZFS dataset %s does not exist' % (name, ))
|
||
|
|
||
|
def assertNotExists(self, name):
|
||
|
self.assertFalse(
|
||
|
lzc.lzc_exists(name), 'ZFS dataset %s exists' % (name, ))
|
||
|
|
||
|
def test_exists(self):
|
||
|
self.assertExists(ZFSTest.pool.makeName())
|
||
|
|
||
|
def test_exists_in_ro_pool(self):
|
||
|
self.assertExists(ZFSTest.readonly_pool.makeName())
|
||
|
|
||
|
def test_exists_failure(self):
|
||
|
self.assertNotExists(ZFSTest.pool.makeName(b'nonexistent'))
|
||
|
|
||
|
def test_create_fs(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/test1")
|
||
|
|
||
|
lzc.lzc_create(name)
|
||
|
self.assertExists(name)
|
||
|
|
||
|
def test_create_zvol(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/zvol")
|
||
|
props = {b"volsize": 1024 * 1024}
|
||
|
|
||
|
lzc.lzc_create(name, ds_type='zvol', props=props)
|
||
|
self.assertExists(name)
|
||
|
# On Gentoo with ZFS 0.6.5.4 the volume is busy
|
||
|
# and can not be destroyed right after its creation.
|
||
|
# A reason for this is unknown at the moment.
|
||
|
# Because of that the post-test clean up could fail.
|
||
|
time.sleep(0.1)
|
||
|
|
||
|
def test_create_fs_with_prop(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/test2")
|
||
|
props = {b"atime": 0}
|
||
|
|
||
|
lzc.lzc_create(name, props=props)
|
||
|
self.assertExists(name)
|
||
|
|
||
|
def test_create_fs_wrong_ds_type(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/test1")
|
||
|
|
||
|
with self.assertRaises(lzc_exc.DatasetTypeInvalid):
|
||
|
lzc.lzc_create(name, ds_type='wrong')
|
||
|
|
||
|
def test_create_fs_below_zvol(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/zvol")
|
||
|
props = {b"volsize": 1024 * 1024}
|
||
|
|
||
|
lzc.lzc_create(name, ds_type='zvol', props=props)
|
||
|
with self.assertRaises(lzc_exc.WrongParent):
|
||
|
lzc.lzc_create(name + b'/fs')
|
||
|
|
||
|
def test_create_zvol_below_zvol(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/zvol")
|
||
|
props = {b"volsize": 1024 * 1024}
|
||
|
|
||
|
lzc.lzc_create(name, ds_type='zvol', props=props)
|
||
|
with self.assertRaises(lzc_exc.WrongParent):
|
||
|
lzc.lzc_create(name + b'/zvol', ds_type='zvol', props=props)
|
||
|
|
||
|
def test_create_fs_duplicate(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/test6")
|
||
|
|
||
|
lzc.lzc_create(name)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.FilesystemExists):
|
||
|
lzc.lzc_create(name)
|
||
|
|
||
|
def test_create_fs_in_ro_pool(self):
|
||
|
name = ZFSTest.readonly_pool.makeName(b"fs")
|
||
|
|
||
|
with self.assertRaises(lzc_exc.ReadOnlyPool):
|
||
|
lzc.lzc_create(name)
|
||
|
|
||
|
def test_create_fs_without_parent(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1/nonexistent/test")
|
||
|
|
||
|
with self.assertRaises(lzc_exc.ParentNotFound):
|
||
|
lzc.lzc_create(name)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_create_fs_in_nonexistent_pool(self):
|
||
|
name = b"no-such-pool/fs"
|
||
|
|
||
|
with self.assertRaises(lzc_exc.ParentNotFound):
|
||
|
lzc.lzc_create(name)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_create_fs_with_invalid_prop(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/test3")
|
||
|
props = {b"BOGUS": 0}
|
||
|
|
||
|
with self.assertRaises(lzc_exc.PropertyInvalid):
|
||
|
lzc.lzc_create(name, 'zfs', props)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_create_fs_with_invalid_prop_type(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/test4")
|
||
|
props = {b"recordsize": b"128k"}
|
||
|
|
||
|
with self.assertRaises(lzc_exc.PropertyInvalid):
|
||
|
lzc.lzc_create(name, 'zfs', props)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_create_fs_with_invalid_prop_val(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/test5")
|
||
|
props = {b"atime": 20}
|
||
|
|
||
|
with self.assertRaises(lzc_exc.PropertyInvalid):
|
||
|
lzc.lzc_create(name, 'zfs', props)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_create_fs_with_invalid_name(self):
|
||
|
name = ZFSTest.pool.makeName(b"@badname")
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_create(name)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_create_fs_with_invalid_pool_name(self):
|
||
|
name = b"bad!pool/fs"
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_create(name)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_create_encrypted_fs(self):
|
||
|
fs = ZFSTest.pool.getFilesystem(b"encrypted")
|
||
|
name = fs.getName()
|
||
|
filename = None
|
||
|
with tempfile.NamedTemporaryFile() as f:
|
||
|
filename = "file://" + f.name
|
||
|
props = {
|
||
|
b"encryption": lzc.zio_encrypt.ZIO_CRYPT_AES_256_CCM,
|
||
|
b"keylocation": filename.encode(),
|
||
|
b"keyformat": lzc.zfs_keyformat.ZFS_KEYFORMAT_RAW,
|
||
|
}
|
||
|
key = os.urandom(lzc.WRAPPING_KEY_LEN)
|
||
|
lzc.lzc_create(name, 'zfs', props=props, key=key)
|
||
|
self.assertEqual(fs.getProperty("encryption"), b"aes-256-ccm")
|
||
|
self.assertEqual(fs.getProperty("encryptionroot"), name)
|
||
|
self.assertEqual(fs.getProperty("keylocation"), filename.encode())
|
||
|
self.assertEqual(fs.getProperty("keyformat"), b"raw")
|
||
|
|
||
|
def test_snapshot(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"@snap")
|
||
|
snaps = [snapname]
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
self.assertExists(snapname)
|
||
|
|
||
|
def test_snapshot_empty_list(self):
|
||
|
lzc.lzc_snapshot([])
|
||
|
|
||
|
def test_snapshot_user_props(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"@snap")
|
||
|
snaps = [snapname]
|
||
|
props = {b"user:foo": b"bar"}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps, props)
|
||
|
self.assertExists(snapname)
|
||
|
|
||
|
def test_snapshot_invalid_props(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"@snap")
|
||
|
snaps = [snapname]
|
||
|
props = {b"foo": b"bar"}
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps, props)
|
||
|
|
||
|
self.assertEqual(len(ctx.exception.errors), len(snaps))
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.PropertyInvalid)
|
||
|
self.assertNotExists(snapname)
|
||
|
|
||
|
def test_snapshot_ro_pool(self):
|
||
|
snapname1 = ZFSTest.readonly_pool.makeName(b"@snap")
|
||
|
snapname2 = ZFSTest.readonly_pool.makeName(b"fs1@snap")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
# NB: one common error is reported.
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.ReadOnlyPool)
|
||
|
self.assertNotExists(snapname1)
|
||
|
self.assertNotExists(snapname2)
|
||
|
|
||
|
def test_snapshot_nonexistent_pool(self):
|
||
|
snapname = b"no-such-pool@snap"
|
||
|
snaps = [snapname]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.FilesystemNotFound)
|
||
|
|
||
|
def test_snapshot_nonexistent_fs(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"nonexistent@snap")
|
||
|
snaps = [snapname]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.FilesystemNotFound)
|
||
|
|
||
|
def test_snapshot_nonexistent_and_existent_fs(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"@snap")
|
||
|
snapname2 = ZFSTest.pool.makeName(b"nonexistent@snap")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.FilesystemNotFound)
|
||
|
self.assertNotExists(snapname1)
|
||
|
self.assertNotExists(snapname2)
|
||
|
|
||
|
def test_multiple_snapshots_nonexistent_fs(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"nonexistent@snap1")
|
||
|
snapname2 = ZFSTest.pool.makeName(b"nonexistent@snap2")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
# XXX two errors should be reported but alas
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.DuplicateSnapshots)
|
||
|
self.assertNotExists(snapname1)
|
||
|
self.assertNotExists(snapname2)
|
||
|
|
||
|
def test_multiple_snapshots_multiple_nonexistent_fs(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"nonexistent1@snap")
|
||
|
snapname2 = ZFSTest.pool.makeName(b"nonexistent2@snap")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
self.assertEqual(len(ctx.exception.errors), 2)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.FilesystemNotFound)
|
||
|
self.assertNotExists(snapname1)
|
||
|
self.assertNotExists(snapname2)
|
||
|
|
||
|
def test_snapshot_already_exists(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"@snap")
|
||
|
snaps = [snapname]
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.SnapshotExists)
|
||
|
|
||
|
def test_multiple_snapshots_for_same_fs(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"@snap1")
|
||
|
snapname2 = ZFSTest.pool.makeName(b"@snap2")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.DuplicateSnapshots)
|
||
|
self.assertNotExists(snapname1)
|
||
|
self.assertNotExists(snapname2)
|
||
|
|
||
|
def test_multiple_snapshots(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"@snap")
|
||
|
snapname2 = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
self.assertExists(snapname1)
|
||
|
self.assertExists(snapname2)
|
||
|
|
||
|
def test_multiple_existing_snapshots(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"@snap")
|
||
|
snapname2 = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
self.assertEqual(len(ctx.exception.errors), 2)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.SnapshotExists)
|
||
|
|
||
|
def test_multiple_new_and_existing_snapshots(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"@snap")
|
||
|
snapname2 = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
snapname3 = ZFSTest.pool.makeName(b"fs2@snap")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
more_snaps = snaps + [snapname3]
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(more_snaps)
|
||
|
|
||
|
self.assertEqual(len(ctx.exception.errors), 2)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.SnapshotExists)
|
||
|
self.assertNotExists(snapname3)
|
||
|
|
||
|
def test_snapshot_multiple_errors(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"@snap")
|
||
|
snapname2 = ZFSTest.pool.makeName(b"nonexistent@snap")
|
||
|
snapname3 = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
snaps = [snapname1]
|
||
|
more_snaps = [snapname1, snapname2, snapname3]
|
||
|
|
||
|
# create 'snapname1' snapshot
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
# attempt to create 3 snapshots:
|
||
|
# 1. duplicate snapshot name
|
||
|
# 2. refers to filesystem that doesn't exist
|
||
|
# 3. could have succeeded if not for 1 and 2
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(more_snaps)
|
||
|
|
||
|
# It seems that FilesystemNotFound overrides the other error,
|
||
|
# but it doesn't have to.
|
||
|
self.assertGreater(len(ctx.exception.errors), 0)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(
|
||
|
e, (lzc_exc.SnapshotExists, lzc_exc.FilesystemNotFound))
|
||
|
self.assertNotExists(snapname2)
|
||
|
self.assertNotExists(snapname3)
|
||
|
|
||
|
def test_snapshot_different_pools(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"@snap")
|
||
|
snapname2 = ZFSTest.misc_pool.makeName(b"@snap")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
# NB: one common error is reported.
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.PoolsDiffer)
|
||
|
self.assertNotExists(snapname1)
|
||
|
self.assertNotExists(snapname2)
|
||
|
|
||
|
def test_snapshot_different_pools_ro_pool(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"@snap")
|
||
|
snapname2 = ZFSTest.readonly_pool.makeName(b"@snap")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
# NB: one common error is reported.
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
# NB: depending on whether the first attempted snapshot is
|
||
|
# for the read-only pool a different error is reported.
|
||
|
self.assertIsInstance(
|
||
|
e, (lzc_exc.PoolsDiffer, lzc_exc.ReadOnlyPool))
|
||
|
self.assertNotExists(snapname1)
|
||
|
self.assertNotExists(snapname2)
|
||
|
|
||
|
def test_snapshot_invalid_name(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"@bad&name")
|
||
|
snapname2 = ZFSTest.pool.makeName(b"fs1@bad*name")
|
||
|
snapname3 = ZFSTest.pool.makeName(b"fs2@snap")
|
||
|
snaps = [snapname1, snapname2, snapname3]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
# NB: one common error is reported.
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameInvalid)
|
||
|
self.assertIsNone(e.name)
|
||
|
|
||
|
def test_snapshot_too_long_complete_name(self):
|
||
|
snapname1 = ZFSTest.pool.makeTooLongName(b"fs1@")
|
||
|
snapname2 = ZFSTest.pool.makeTooLongName(b"fs2@")
|
||
|
snapname3 = ZFSTest.pool.makeName(b"@snap")
|
||
|
snaps = [snapname1, snapname2, snapname3]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
self.assertEqual(len(ctx.exception.errors), 2)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameTooLong)
|
||
|
self.assertIsNotNone(e.name)
|
||
|
|
||
|
def test_snapshot_too_long_snap_name(self):
|
||
|
snapname1 = ZFSTest.pool.makeTooLongComponent(b"fs1@")
|
||
|
snapname2 = ZFSTest.pool.makeTooLongComponent(b"fs2@")
|
||
|
snapname3 = ZFSTest.pool.makeName(b"@snap")
|
||
|
snaps = [snapname1, snapname2, snapname3]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotFailure) as ctx:
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
|
||
|
# NB: one common error is reported.
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameTooLong)
|
||
|
self.assertIsNone(e.name)
|
||
|
|
||
|
def test_destroy_nonexistent_snapshot(self):
|
||
|
lzc.lzc_destroy_snaps([ZFSTest.pool.makeName(b"@nonexistent")], False)
|
||
|
lzc.lzc_destroy_snaps([ZFSTest.pool.makeName(b"@nonexistent")], True)
|
||
|
|
||
|
def test_destroy_snapshot_of_nonexistent_pool(self):
|
||
|
with self.assertRaises(lzc_exc.SnapshotDestructionFailure) as ctx:
|
||
|
lzc.lzc_destroy_snaps([b"no-such-pool@snap"], False)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.PoolNotFound)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotDestructionFailure) as ctx:
|
||
|
lzc.lzc_destroy_snaps([b"no-such-pool@snap"], True)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.PoolNotFound)
|
||
|
|
||
|
# NB: note the difference from the nonexistent pool test.
|
||
|
def test_destroy_snapshot_of_nonexistent_fs(self):
|
||
|
lzc.lzc_destroy_snaps(
|
||
|
[ZFSTest.pool.makeName(b"nonexistent@snap")], False)
|
||
|
lzc.lzc_destroy_snaps(
|
||
|
[ZFSTest.pool.makeName(b"nonexistent@snap")], True)
|
||
|
|
||
|
# Apparently the name is not checked for validity.
|
||
|
@unittest.expectedFailure
|
||
|
def test_destroy_invalid_snap_name(self):
|
||
|
with self.assertRaises(lzc_exc.SnapshotDestructionFailure):
|
||
|
lzc.lzc_destroy_snaps(
|
||
|
[ZFSTest.pool.makeName(b"@non$&*existent")], False)
|
||
|
with self.assertRaises(lzc_exc.SnapshotDestructionFailure):
|
||
|
lzc.lzc_destroy_snaps(
|
||
|
[ZFSTest.pool.makeName(b"@non$&*existent")], True)
|
||
|
|
||
|
# Apparently the full name is not checked for length.
|
||
|
@unittest.expectedFailure
|
||
|
def test_destroy_too_long_full_snap_name(self):
|
||
|
snapname1 = ZFSTest.pool.makeTooLongName(b"fs1@")
|
||
|
snaps = [snapname1]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotDestructionFailure):
|
||
|
lzc.lzc_destroy_snaps(snaps, False)
|
||
|
with self.assertRaises(lzc_exc.SnapshotDestructionFailure):
|
||
|
lzc.lzc_destroy_snaps(snaps, True)
|
||
|
|
||
|
def test_destroy_too_long_short_snap_name(self):
|
||
|
snapname1 = ZFSTest.pool.makeTooLongComponent(b"fs1@")
|
||
|
snapname2 = ZFSTest.pool.makeTooLongComponent(b"fs2@")
|
||
|
snapname3 = ZFSTest.pool.makeName(b"@snap")
|
||
|
snaps = [snapname1, snapname2, snapname3]
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotDestructionFailure) as ctx:
|
||
|
lzc.lzc_destroy_snaps(snaps, False)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameTooLong)
|
||
|
|
||
|
@unittest.skipUnless(*snap_always_unmounted_before_destruction())
|
||
|
def test_destroy_mounted_snap(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
with zfs_mount(snap):
|
||
|
# the snapshot should be force-unmounted
|
||
|
lzc.lzc_destroy_snaps([snap], defer=False)
|
||
|
self.assertNotExists(snap)
|
||
|
|
||
|
def test_clone(self):
|
||
|
# NB: note the special name for the snapshot.
|
||
|
# Since currently we can not destroy filesystems,
|
||
|
# it would be impossible to destroy the snapshot,
|
||
|
# so no point in attempting to clean it up.
|
||
|
snapname = ZFSTest.pool.makeName(b"fs2@origin1")
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/clone1")
|
||
|
|
||
|
lzc.lzc_snapshot([snapname])
|
||
|
|
||
|
lzc.lzc_clone(name, snapname)
|
||
|
self.assertExists(name)
|
||
|
|
||
|
def test_clone_nonexistent_snapshot(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"fs2@nonexistent")
|
||
|
name = ZFSTest.pool.makeName(b"fs1/fs/clone2")
|
||
|
|
||
|
# XXX The error should be SnapshotNotFound
|
||
|
# but limitations of C interface do not allow
|
||
|
# to differentiate between the errors.
|
||
|
with self.assertRaises(lzc_exc.DatasetNotFound):
|
||
|
lzc.lzc_clone(name, snapname)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_clone_nonexistent_parent_fs(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"fs2@origin3")
|
||
|
name = ZFSTest.pool.makeName(b"fs1/nonexistent/clone3")
|
||
|
|
||
|
lzc.lzc_snapshot([snapname])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.DatasetNotFound):
|
||
|
lzc.lzc_clone(name, snapname)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_clone_to_nonexistent_pool(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"fs2@snap")
|
||
|
name = b"no-such-pool/fs"
|
||
|
|
||
|
lzc.lzc_snapshot([snapname])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.DatasetNotFound):
|
||
|
lzc.lzc_clone(name, snapname)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_clone_invalid_snap_name(self):
|
||
|
# Use a valid filesystem name of filesystem that
|
||
|
# exists as a snapshot name
|
||
|
snapname = ZFSTest.pool.makeName(b"fs1/fs")
|
||
|
name = ZFSTest.pool.makeName(b"fs2/clone")
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotNameInvalid):
|
||
|
lzc.lzc_clone(name, snapname)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_clone_invalid_snap_name_2(self):
|
||
|
# Use a valid filesystem name of filesystem that
|
||
|
# doesn't exist as a snapshot name
|
||
|
snapname = ZFSTest.pool.makeName(b"fs1/nonexistent")
|
||
|
name = ZFSTest.pool.makeName(b"fs2/clone")
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotNameInvalid):
|
||
|
lzc.lzc_clone(name, snapname)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_clone_invalid_name(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"fs2@snap")
|
||
|
name = ZFSTest.pool.makeName(b"fs1/bad#name")
|
||
|
|
||
|
lzc.lzc_snapshot([snapname])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.FilesystemNameInvalid):
|
||
|
lzc.lzc_clone(name, snapname)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_clone_invalid_pool_name(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"fs2@snap")
|
||
|
name = b"bad!pool/fs1"
|
||
|
|
||
|
lzc.lzc_snapshot([snapname])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.FilesystemNameInvalid):
|
||
|
lzc.lzc_clone(name, snapname)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_clone_across_pools(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"fs2@snap")
|
||
|
name = ZFSTest.misc_pool.makeName(b"clone1")
|
||
|
|
||
|
lzc.lzc_snapshot([snapname])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.PoolsDiffer):
|
||
|
lzc.lzc_clone(name, snapname)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_clone_across_pools_to_ro_pool(self):
|
||
|
snapname = ZFSTest.pool.makeName(b"fs2@snap")
|
||
|
name = ZFSTest.readonly_pool.makeName(b"fs1/clone1")
|
||
|
|
||
|
lzc.lzc_snapshot([snapname])
|
||
|
|
||
|
# it's legal to report either of the conditions
|
||
|
with self.assertRaises((lzc_exc.ReadOnlyPool, lzc_exc.PoolsDiffer)):
|
||
|
lzc.lzc_clone(name, snapname)
|
||
|
self.assertNotExists(name)
|
||
|
|
||
|
def test_destroy_cloned_fs(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"fs2@origin4")
|
||
|
snapname2 = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
clonename = ZFSTest.pool.makeName(b"fs1/fs/clone4")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
lzc.lzc_clone(clonename, snapname1)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotDestructionFailure) as ctx:
|
||
|
lzc.lzc_destroy_snaps(snaps, False)
|
||
|
|
||
|
self.assertEqual(len(ctx.exception.errors), 1)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.SnapshotIsCloned)
|
||
|
for snap in snaps:
|
||
|
self.assertExists(snap)
|
||
|
|
||
|
def test_deferred_destroy_cloned_fs(self):
|
||
|
snapname1 = ZFSTest.pool.makeName(b"fs2@origin5")
|
||
|
snapname2 = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
clonename = ZFSTest.pool.makeName(b"fs1/fs/clone5")
|
||
|
snaps = [snapname1, snapname2]
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
lzc.lzc_clone(clonename, snapname1)
|
||
|
|
||
|
lzc.lzc_destroy_snaps(snaps, defer=True)
|
||
|
|
||
|
self.assertExists(snapname1)
|
||
|
self.assertNotExists(snapname2)
|
||
|
|
||
|
def test_rollback(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1")
|
||
|
snapname = name + b"@snap"
|
||
|
|
||
|
lzc.lzc_snapshot([snapname])
|
||
|
ret = lzc.lzc_rollback(name)
|
||
|
self.assertEqual(ret, snapname)
|
||
|
|
||
|
def test_rollback_2(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1")
|
||
|
snapname1 = name + b"@snap1"
|
||
|
snapname2 = name + b"@snap2"
|
||
|
|
||
|
lzc.lzc_snapshot([snapname1])
|
||
|
lzc.lzc_snapshot([snapname2])
|
||
|
ret = lzc.lzc_rollback(name)
|
||
|
self.assertEqual(ret, snapname2)
|
||
|
|
||
|
def test_rollback_no_snaps(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1")
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotNotFound):
|
||
|
lzc.lzc_rollback(name)
|
||
|
|
||
|
def test_rollback_non_existent_fs(self):
|
||
|
name = ZFSTest.pool.makeName(b"nonexistent")
|
||
|
|
||
|
with self.assertRaises(lzc_exc.FilesystemNotFound):
|
||
|
lzc.lzc_rollback(name)
|
||
|
|
||
|
def test_rollback_invalid_fs_name(self):
|
||
|
name = ZFSTest.pool.makeName(b"bad~name")
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_rollback(name)
|
||
|
|
||
|
def test_rollback_snap_name(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_rollback(name)
|
||
|
|
||
|
def test_rollback_snap_name_2(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
|
||
|
lzc.lzc_snapshot([name])
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_rollback(name)
|
||
|
|
||
|
def test_rollback_too_long_fs_name(self):
|
||
|
name = ZFSTest.pool.makeTooLongName()
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameTooLong):
|
||
|
lzc.lzc_rollback(name)
|
||
|
|
||
|
def test_rollback_to_snap_name(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1")
|
||
|
snap = name + b"@snap"
|
||
|
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
lzc.lzc_rollback_to(name, snap)
|
||
|
|
||
|
def test_rollback_to_not_latest(self):
|
||
|
fsname = ZFSTest.pool.makeName(b'fs1')
|
||
|
snap1 = fsname + b"@snap1"
|
||
|
snap2 = fsname + b"@snap2"
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
with self.assertRaises(lzc_exc.SnapshotNotLatest):
|
||
|
lzc.lzc_rollback_to(fsname, fsname + b"@snap1")
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks(self):
|
||
|
snaps = [ZFSTest.pool.makeName(
|
||
|
b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeName(
|
||
|
b'fs1#bmark1'), ZFSTest.pool.makeName(b'fs2#bmark1')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_2(self):
|
||
|
snaps = [ZFSTest.pool.makeName(
|
||
|
b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeName(
|
||
|
b'fs1#bmark1'), ZFSTest.pool.makeName(b'fs2#bmark1')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
lzc.lzc_destroy_snaps(snaps, defer=False)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_empty(self):
|
||
|
lzc.lzc_bookmark({})
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_mismatching_name(self):
|
||
|
snaps = [ZFSTest.pool.makeName(b'fs1@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeName(b'fs2#bmark1')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.BookmarkMismatch)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_invalid_name(self):
|
||
|
snaps = [ZFSTest.pool.makeName(b'fs1@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeName(b'fs1#bmark!')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameInvalid)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_invalid_name_2(self):
|
||
|
snaps = [ZFSTest.pool.makeName(b'fs1@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeName(b'fs1@bmark')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameInvalid)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_too_long_name(self):
|
||
|
snaps = [ZFSTest.pool.makeName(b'fs1@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeTooLongName(b'fs1#')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameTooLong)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_too_long_name_2(self):
|
||
|
snaps = [ZFSTest.pool.makeName(b'fs1@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeTooLongComponent(b'fs1#')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameTooLong)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_mismatching_names(self):
|
||
|
snaps = [ZFSTest.pool.makeName(
|
||
|
b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeName(
|
||
|
b'fs2#bmark1'), ZFSTest.pool.makeName(b'fs1#bmark1')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.BookmarkMismatch)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_partially_mismatching_names(self):
|
||
|
snaps = [ZFSTest.pool.makeName(
|
||
|
b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeName(
|
||
|
b'fs2#bmark'), ZFSTest.pool.makeName(b'fs2#bmark1')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps)
|
||
|
with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.BookmarkMismatch)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_cross_pool(self):
|
||
|
snaps = [ZFSTest.pool.makeName(
|
||
|
b'fs1@snap1'), ZFSTest.misc_pool.makeName(b'@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeName(
|
||
|
b'fs1#bmark1'), ZFSTest.misc_pool.makeName(b'#bmark1')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps[0:1])
|
||
|
lzc.lzc_snapshot(snaps[1:2])
|
||
|
with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.PoolsDiffer)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_missing_snap(self):
|
||
|
snaps = [ZFSTest.pool.makeName(
|
||
|
b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeName(
|
||
|
b'fs1#bmark1'), ZFSTest.pool.makeName(b'fs2#bmark1')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
lzc.lzc_snapshot(snaps[0:1])
|
||
|
with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.SnapshotNotFound)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_missing_snaps(self):
|
||
|
snaps = [ZFSTest.pool.makeName(
|
||
|
b'fs1@snap1'), ZFSTest.pool.makeName(b'fs2@snap1')]
|
||
|
bmarks = [ZFSTest.pool.makeName(
|
||
|
b'fs1#bmark1'), ZFSTest.pool.makeName(b'fs2#bmark1')]
|
||
|
bmark_dict = {x: y for x, y in zip(bmarks, snaps)}
|
||
|
|
||
|
with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.SnapshotNotFound)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_for_the_same_snap(self):
|
||
|
snap = ZFSTest.pool.makeName(b'fs1@snap1')
|
||
|
bmark1 = ZFSTest.pool.makeName(b'fs1#bmark1')
|
||
|
bmark2 = ZFSTest.pool.makeName(b'fs1#bmark2')
|
||
|
bmark_dict = {bmark1: snap, bmark2: snap}
|
||
|
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_for_the_same_snap_2(self):
|
||
|
snap = ZFSTest.pool.makeName(b'fs1@snap1')
|
||
|
bmark1 = ZFSTest.pool.makeName(b'fs1#bmark1')
|
||
|
bmark2 = ZFSTest.pool.makeName(b'fs1#bmark2')
|
||
|
bmark_dict1 = {bmark1: snap}
|
||
|
bmark_dict2 = {bmark2: snap}
|
||
|
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
lzc.lzc_bookmark(bmark_dict1)
|
||
|
lzc.lzc_bookmark(bmark_dict2)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_bookmarks_duplicate_name(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b'fs1@snap1')
|
||
|
snap2 = ZFSTest.pool.makeName(b'fs1@snap2')
|
||
|
bmark = ZFSTest.pool.makeName(b'fs1#bmark')
|
||
|
bmark_dict1 = {bmark: snap1}
|
||
|
bmark_dict2 = {bmark: snap2}
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
lzc.lzc_bookmark(bmark_dict1)
|
||
|
with self.assertRaises(lzc_exc.BookmarkFailure) as ctx:
|
||
|
lzc.lzc_bookmark(bmark_dict2)
|
||
|
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.BookmarkExists)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_get_bookmarks(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b'fs1@snap1')
|
||
|
snap2 = ZFSTest.pool.makeName(b'fs1@snap2')
|
||
|
bmark = ZFSTest.pool.makeName(b'fs1#bmark')
|
||
|
bmark1 = ZFSTest.pool.makeName(b'fs1#bmark1')
|
||
|
bmark2 = ZFSTest.pool.makeName(b'fs1#bmark2')
|
||
|
bmark_dict1 = {bmark1: snap1, bmark2: snap2}
|
||
|
bmark_dict2 = {bmark: snap2}
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
lzc.lzc_bookmark(bmark_dict1)
|
||
|
lzc.lzc_bookmark(bmark_dict2)
|
||
|
lzc.lzc_destroy_snaps([snap1, snap2], defer=False)
|
||
|
|
||
|
bmarks = lzc.lzc_get_bookmarks(ZFSTest.pool.makeName(b'fs1'))
|
||
|
self.assertEqual(len(bmarks), 3)
|
||
|
for b in b'bmark', b'bmark1', b'bmark2':
|
||
|
self.assertIn(b, bmarks)
|
||
|
self.assertIsInstance(bmarks[b], dict)
|
||
|
self.assertEqual(len(bmarks[b]), 0)
|
||
|
|
||
|
bmarks = lzc.lzc_get_bookmarks(ZFSTest.pool.makeName(b'fs1'),
|
||
|
[b'guid', b'createtxg', b'creation'])
|
||
|
self.assertEqual(len(bmarks), 3)
|
||
|
for b in b'bmark', b'bmark1', b'bmark2':
|
||
|
self.assertIn(b, bmarks)
|
||
|
self.assertIsInstance(bmarks[b], dict)
|
||
|
self.assertEqual(len(bmarks[b]), 3)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_get_bookmarks_invalid_property(self):
|
||
|
snap = ZFSTest.pool.makeName(b'fs1@snap')
|
||
|
bmark = ZFSTest.pool.makeName(b'fs1#bmark')
|
||
|
bmark_dict = {bmark: snap}
|
||
|
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
bmarks = lzc.lzc_get_bookmarks(
|
||
|
ZFSTest.pool.makeName(b'fs1'), [b'badprop'])
|
||
|
self.assertEqual(len(bmarks), 1)
|
||
|
for b in (b'bmark', ):
|
||
|
self.assertIn(b, bmarks)
|
||
|
self.assertIsInstance(bmarks[b], dict)
|
||
|
self.assertEqual(len(bmarks[b]), 0)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_get_bookmarks_nonexistent_fs(self):
|
||
|
with self.assertRaises(lzc_exc.FilesystemNotFound):
|
||
|
lzc.lzc_get_bookmarks(ZFSTest.pool.makeName(b'nonexistent'))
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_destroy_bookmarks(self):
|
||
|
snap = ZFSTest.pool.makeName(b'fs1@snap')
|
||
|
bmark = ZFSTest.pool.makeName(b'fs1#bmark')
|
||
|
bmark_dict = {bmark: snap}
|
||
|
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
lzc.lzc_destroy_bookmarks(
|
||
|
[bmark, ZFSTest.pool.makeName(b'fs1#nonexistent')])
|
||
|
bmarks = lzc.lzc_get_bookmarks(ZFSTest.pool.makeName(b'fs1'))
|
||
|
self.assertEqual(len(bmarks), 0)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_destroy_bookmarks_invalid_name(self):
|
||
|
snap = ZFSTest.pool.makeName(b'fs1@snap')
|
||
|
bmark = ZFSTest.pool.makeName(b'fs1#bmark')
|
||
|
bmark_dict = {bmark: snap}
|
||
|
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
lzc.lzc_bookmark(bmark_dict)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.BookmarkDestructionFailure) as ctx:
|
||
|
lzc.lzc_destroy_bookmarks(
|
||
|
[bmark, ZFSTest.pool.makeName(b'fs1/nonexistent')])
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameInvalid)
|
||
|
|
||
|
bmarks = lzc.lzc_get_bookmarks(ZFSTest.pool.makeName(b'fs1'))
|
||
|
self.assertEqual(len(bmarks), 1)
|
||
|
self.assertIn(b'bmark', bmarks)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_destroy_bookmark_nonexistent_fs(self):
|
||
|
lzc.lzc_destroy_bookmarks(
|
||
|
[ZFSTest.pool.makeName(b'nonexistent#bmark')])
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_destroy_bookmarks_empty(self):
|
||
|
lzc.lzc_bookmark({})
|
||
|
|
||
|
def test_snaprange_space(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
snap3 = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
lzc.lzc_snapshot([snap3])
|
||
|
|
||
|
space = lzc.lzc_snaprange_space(snap1, snap2)
|
||
|
self.assertIsInstance(space, (int, int))
|
||
|
space = lzc.lzc_snaprange_space(snap2, snap3)
|
||
|
self.assertIsInstance(space, (int, int))
|
||
|
space = lzc.lzc_snaprange_space(snap1, snap3)
|
||
|
self.assertIsInstance(space, (int, int))
|
||
|
|
||
|
def test_snaprange_space_2(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
snap3 = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
|
||
|
with tempfile.NamedTemporaryFile(dir=mntdir) as f:
|
||
|
for i in range(1024):
|
||
|
f.write(b'x' * 1024)
|
||
|
f.flush()
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
lzc.lzc_snapshot([snap3])
|
||
|
|
||
|
space = lzc.lzc_snaprange_space(snap1, snap2)
|
||
|
self.assertGreater(space, 1024 * 1024)
|
||
|
space = lzc.lzc_snaprange_space(snap2, snap3)
|
||
|
self.assertGreater(space, 1024 * 1024)
|
||
|
space = lzc.lzc_snaprange_space(snap1, snap3)
|
||
|
self.assertGreater(space, 1024 * 1024)
|
||
|
|
||
|
def test_snaprange_space_same_snap(self):
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
|
||
|
with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
|
||
|
with tempfile.NamedTemporaryFile(dir=mntdir) as f:
|
||
|
for i in range(1024):
|
||
|
f.write(b'x' * 1024)
|
||
|
f.flush()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
space = lzc.lzc_snaprange_space(snap, snap)
|
||
|
self.assertGreater(space, 1024 * 1024)
|
||
|
self.assertAlmostEqual(space, 1024 * 1024, delta=1024 * 1024 // 20)
|
||
|
|
||
|
def test_snaprange_space_wrong_order(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotMismatch):
|
||
|
lzc.lzc_snaprange_space(snap2, snap1)
|
||
|
|
||
|
def test_snaprange_space_unrelated(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs2@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotMismatch):
|
||
|
lzc.lzc_snaprange_space(snap1, snap2)
|
||
|
|
||
|
def test_snaprange_space_across_pools(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.misc_pool.makeName(b"@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.PoolsDiffer):
|
||
|
lzc.lzc_snaprange_space(snap1, snap2)
|
||
|
|
||
|
def test_snaprange_space_nonexistent(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
|
||
|
lzc.lzc_snaprange_space(snap1, snap2)
|
||
|
self.assertEqual(ctx.exception.name, snap2)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
|
||
|
lzc.lzc_snaprange_space(snap2, snap1)
|
||
|
self.assertEqual(ctx.exception.name, snap1)
|
||
|
|
||
|
def test_snaprange_space_invalid_name(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@sn#p")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_snaprange_space(snap1, snap2)
|
||
|
|
||
|
def test_snaprange_space_not_snap(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_snaprange_space(snap1, snap2)
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_snaprange_space(snap2, snap1)
|
||
|
|
||
|
def test_snaprange_space_not_snap_2(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1#bmark")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_snaprange_space(snap1, snap2)
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_snaprange_space(snap2, snap1)
|
||
|
|
||
|
def test_send_space(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
snap3 = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
lzc.lzc_snapshot([snap3])
|
||
|
|
||
|
space = lzc.lzc_send_space(snap2, snap1)
|
||
|
self.assertIsInstance(space, (int, int))
|
||
|
space = lzc.lzc_send_space(snap3, snap2)
|
||
|
self.assertIsInstance(space, (int, int))
|
||
|
space = lzc.lzc_send_space(snap3, snap1)
|
||
|
self.assertIsInstance(space, (int, int))
|
||
|
space = lzc.lzc_send_space(snap1)
|
||
|
self.assertIsInstance(space, (int, int))
|
||
|
space = lzc.lzc_send_space(snap2)
|
||
|
self.assertIsInstance(space, (int, int))
|
||
|
space = lzc.lzc_send_space(snap3)
|
||
|
self.assertIsInstance(space, (int, int))
|
||
|
|
||
|
def test_send_space_2(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
snap3 = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
|
||
|
with tempfile.NamedTemporaryFile(dir=mntdir) as f:
|
||
|
for i in range(1024):
|
||
|
f.write(b'x' * 1024)
|
||
|
f.flush()
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
lzc.lzc_snapshot([snap3])
|
||
|
|
||
|
space = lzc.lzc_send_space(snap2, snap1)
|
||
|
self.assertGreater(space, 1024 * 1024)
|
||
|
|
||
|
space = lzc.lzc_send_space(snap3, snap2)
|
||
|
|
||
|
space = lzc.lzc_send_space(snap3, snap1)
|
||
|
|
||
|
space_empty = lzc.lzc_send_space(snap1)
|
||
|
|
||
|
space = lzc.lzc_send_space(snap2)
|
||
|
self.assertGreater(space, 1024 * 1024)
|
||
|
|
||
|
space = lzc.lzc_send_space(snap3)
|
||
|
self.assertEqual(space, space_empty)
|
||
|
|
||
|
def test_send_space_same_snap(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
with self.assertRaises(lzc_exc.SnapshotMismatch):
|
||
|
lzc.lzc_send_space(snap1, snap1)
|
||
|
|
||
|
def test_send_space_wrong_order(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotMismatch):
|
||
|
lzc.lzc_send_space(snap1, snap2)
|
||
|
|
||
|
def test_send_space_unrelated(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs2@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotMismatch):
|
||
|
lzc.lzc_send_space(snap1, snap2)
|
||
|
|
||
|
def test_send_space_across_pools(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.misc_pool.makeName(b"@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.PoolsDiffer):
|
||
|
lzc.lzc_send_space(snap1, snap2)
|
||
|
|
||
|
def test_send_space_nonexistent(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs2@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
|
||
|
lzc.lzc_send_space(snap1, snap2)
|
||
|
self.assertEqual(ctx.exception.name, snap1)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
|
||
|
lzc.lzc_send_space(snap2, snap1)
|
||
|
self.assertEqual(ctx.exception.name, snap2)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
|
||
|
lzc.lzc_send_space(snap2)
|
||
|
self.assertEqual(ctx.exception.name, snap2)
|
||
|
|
||
|
def test_send_space_invalid_name(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@sn!p")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameInvalid) as ctx:
|
||
|
lzc.lzc_send_space(snap2, snap1)
|
||
|
self.assertEqual(ctx.exception.name, snap2)
|
||
|
with self.assertRaises(lzc_exc.NameInvalid) as ctx:
|
||
|
lzc.lzc_send_space(snap2)
|
||
|
self.assertEqual(ctx.exception.name, snap2)
|
||
|
with self.assertRaises(lzc_exc.NameInvalid) as ctx:
|
||
|
lzc.lzc_send_space(snap1, snap2)
|
||
|
self.assertEqual(ctx.exception.name, snap2)
|
||
|
|
||
|
def test_send_space_not_snap(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_send_space(snap1, snap2)
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_send_space(snap2, snap1)
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_send_space(snap2)
|
||
|
|
||
|
def test_send_space_not_snap_2(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1#bmark")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_send_space(snap2, snap1)
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_send_space(snap2)
|
||
|
|
||
|
def test_send_full(self):
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
|
||
|
with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
|
||
|
with tempfile.NamedTemporaryFile(dir=mntdir) as f:
|
||
|
for i in range(1024):
|
||
|
f.write(b'x' * 1024)
|
||
|
f.flush()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
estimate = lzc.lzc_send_space(snap)
|
||
|
|
||
|
fd = output.fileno()
|
||
|
lzc.lzc_send(snap, None, fd)
|
||
|
st = os.fstat(fd)
|
||
|
# 5%, arbitrary.
|
||
|
self.assertAlmostEqual(st.st_size, estimate, delta=estimate // 20)
|
||
|
|
||
|
def test_send_incremental(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
|
||
|
with tempfile.NamedTemporaryFile(dir=mntdir) as f:
|
||
|
for i in range(1024):
|
||
|
f.write(b'x' * 1024)
|
||
|
f.flush()
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
estimate = lzc.lzc_send_space(snap2, snap1)
|
||
|
|
||
|
fd = output.fileno()
|
||
|
lzc.lzc_send(snap2, snap1, fd)
|
||
|
st = os.fstat(fd)
|
||
|
# 5%, arbitrary.
|
||
|
self.assertAlmostEqual(st.st_size, estimate, delta=estimate // 20)
|
||
|
|
||
|
def test_send_flags(self):
|
||
|
flags = ['embedded_data', 'large_blocks', 'compress', 'raw']
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
for c in range(len(flags)):
|
||
|
for flag in itertools.permutations(flags, c + 1):
|
||
|
with dev_null() as fd:
|
||
|
lzc.lzc_send(snap, None, fd, list(flag))
|
||
|
|
||
|
def test_send_unknown_flags(self):
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
with dev_null() as fd:
|
||
|
with self.assertRaises(lzc_exc.UnknownStreamFeature):
|
||
|
lzc.lzc_send(snap, None, fd, ['embedded_data', 'UNKNOWN'])
|
||
|
|
||
|
def test_send_same_snap(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
fd = output.fileno()
|
||
|
with self.assertRaises(lzc_exc.SnapshotMismatch):
|
||
|
lzc.lzc_send(snap1, snap1, fd)
|
||
|
|
||
|
def test_send_wrong_order(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
fd = output.fileno()
|
||
|
with self.assertRaises(lzc_exc.SnapshotMismatch):
|
||
|
lzc.lzc_send(snap1, snap2, fd)
|
||
|
|
||
|
def test_send_unrelated(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs2@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
fd = output.fileno()
|
||
|
with self.assertRaises(lzc_exc.SnapshotMismatch):
|
||
|
lzc.lzc_send(snap1, snap2, fd)
|
||
|
|
||
|
def test_send_across_pools(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.misc_pool.makeName(b"@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
fd = output.fileno()
|
||
|
with self.assertRaises(lzc_exc.PoolsDiffer):
|
||
|
lzc.lzc_send(snap1, snap2, fd)
|
||
|
|
||
|
def test_send_nonexistent(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
fd = output.fileno()
|
||
|
with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
|
||
|
lzc.lzc_send(snap1, snap2, fd)
|
||
|
self.assertEqual(ctx.exception.name, snap1)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
|
||
|
lzc.lzc_send(snap2, snap1, fd)
|
||
|
self.assertEqual(ctx.exception.name, snap2)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotNotFound) as ctx:
|
||
|
lzc.lzc_send(snap2, None, fd)
|
||
|
self.assertEqual(ctx.exception.name, snap2)
|
||
|
|
||
|
def test_send_invalid_name(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@sn!p")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
fd = output.fileno()
|
||
|
with self.assertRaises(lzc_exc.NameInvalid) as ctx:
|
||
|
lzc.lzc_send(snap2, snap1, fd)
|
||
|
self.assertEqual(ctx.exception.name, snap2)
|
||
|
with self.assertRaises(lzc_exc.NameInvalid) as ctx:
|
||
|
lzc.lzc_send(snap2, None, fd)
|
||
|
self.assertEqual(ctx.exception.name, snap2)
|
||
|
with self.assertRaises(lzc_exc.NameInvalid) as ctx:
|
||
|
lzc.lzc_send(snap1, snap2, fd)
|
||
|
self.assertEqual(ctx.exception.name, snap2)
|
||
|
|
||
|
# XXX Although undocumented the API allows to create an incremental
|
||
|
# or full stream for a filesystem as if a temporary unnamed snapshot
|
||
|
# is taken at some time after the call is made and before the stream
|
||
|
# starts being produced.
|
||
|
def test_send_filesystem(self):
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
fs = ZFSTest.pool.makeName(b"fs1")
|
||
|
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
fd = output.fileno()
|
||
|
lzc.lzc_send(fs, snap, fd)
|
||
|
lzc.lzc_send(fs, None, fd)
|
||
|
|
||
|
def test_send_from_filesystem(self):
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
fs = ZFSTest.pool.makeName(b"fs1")
|
||
|
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
fd = output.fileno()
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_send(snap, fs, fd)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_send_bookmark(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
bmark = ZFSTest.pool.makeName(b"fs1#bmark")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
lzc.lzc_bookmark({bmark: snap2})
|
||
|
lzc.lzc_destroy_snaps([snap2], defer=False)
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
fd = output.fileno()
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_send(bmark, snap1, fd)
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_send(bmark, None, fd)
|
||
|
|
||
|
@skipUnlessBookmarksSupported
|
||
|
def test_send_from_bookmark(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
bmark = ZFSTest.pool.makeName(b"fs1#bmark")
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
lzc.lzc_bookmark({bmark: snap1})
|
||
|
lzc.lzc_destroy_snaps([snap1], defer=False)
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as output:
|
||
|
fd = output.fileno()
|
||
|
lzc.lzc_send(snap2, bmark, fd)
|
||
|
|
||
|
def test_send_bad_fd(self):
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with tempfile.TemporaryFile() as tmp:
|
||
|
bad_fd = tmp.fileno()
|
||
|
|
||
|
with self.assertRaises(lzc_exc.StreamIOError) as ctx:
|
||
|
lzc.lzc_send(snap, None, bad_fd)
|
||
|
self.assertEqual(ctx.exception.errno, errno.EBADF)
|
||
|
|
||
|
def test_send_bad_fd_2(self):
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.StreamIOError) as ctx:
|
||
|
lzc.lzc_send(snap, None, -2)
|
||
|
self.assertEqual(ctx.exception.errno, errno.EBADF)
|
||
|
|
||
|
def test_send_bad_fd_3(self):
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with tempfile.TemporaryFile() as tmp:
|
||
|
bad_fd = tmp.fileno()
|
||
|
|
||
|
(soft, hard) = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||
|
bad_fd = hard + 1
|
||
|
with self.assertRaises(lzc_exc.StreamIOError) as ctx:
|
||
|
lzc.lzc_send(snap, None, bad_fd)
|
||
|
self.assertEqual(ctx.exception.errno, errno.EBADF)
|
||
|
|
||
|
def test_send_to_broken_pipe(self):
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
if sys.version_info < (3, 0):
|
||
|
proc = subprocess.Popen(['true'], stdin=subprocess.PIPE)
|
||
|
proc.wait()
|
||
|
with self.assertRaises(lzc_exc.StreamIOError) as ctx:
|
||
|
lzc.lzc_send(snap, None, proc.stdin.fileno())
|
||
|
self.assertEqual(ctx.exception.errno, errno.EPIPE)
|
||
|
else:
|
||
|
with subprocess.Popen(['true'], stdin=subprocess.PIPE) as proc:
|
||
|
proc.wait()
|
||
|
with self.assertRaises(lzc_exc.StreamIOError) as ctx:
|
||
|
lzc.lzc_send(snap, None, proc.stdin.fileno())
|
||
|
self.assertEqual(ctx.exception.errno, errno.EPIPE)
|
||
|
|
||
|
def test_send_to_broken_pipe_2(self):
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
|
||
|
with tempfile.NamedTemporaryFile(dir=mntdir) as f:
|
||
|
for i in range(1024):
|
||
|
f.write(b'x' * 1024)
|
||
|
f.flush()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
if sys.version_info < (3, 0):
|
||
|
p = subprocess.Popen(['sleep', '2'], stdin=subprocess.PIPE)
|
||
|
with self.assertRaises(lzc_exc.StreamIOError) as ctx:
|
||
|
lzc.lzc_send(snap, None, p.stdin.fileno())
|
||
|
self.assertTrue(ctx.exception.errno == errno.EPIPE or
|
||
|
ctx.exception.errno == errno.EINTR)
|
||
|
else:
|
||
|
with subprocess.Popen(['sleep', '2'], stdin=subprocess.PIPE) as p:
|
||
|
with self.assertRaises(lzc_exc.StreamIOError) as ctx:
|
||
|
lzc.lzc_send(snap, None, p.stdin.fileno())
|
||
|
self.assertTrue(ctx.exception.errno == errno.EPIPE or
|
||
|
ctx.exception.errno == errno.EINTR)
|
||
|
|
||
|
def test_send_to_ro_file(self):
|
||
|
snap = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with tempfile.NamedTemporaryFile(
|
||
|
suffix='.zstream', delete=False) as output:
|
||
|
# tempfile always opens a temporary file in read-write mode
|
||
|
# regardless of the specified mode, so we have to open it again.
|
||
|
os.chmod(output.name, stat.S_IRUSR)
|
||
|
fd = os.open(output.name, os.O_RDONLY)
|
||
|
with self.assertRaises(lzc_exc.StreamIOError) as ctx:
|
||
|
lzc.lzc_send(snap, None, fd)
|
||
|
os.close(fd)
|
||
|
self.assertEqual(ctx.exception.errno, errno.EBADF)
|
||
|
|
||
|
def test_recv_full(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dst = ZFSTest.pool.makeName(b"fs2/received-1@snap")
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")) as name:
|
||
|
lzc.lzc_snapshot([src])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(dst, stream.fileno())
|
||
|
|
||
|
name = os.path.basename(name)
|
||
|
with zfs_mount(src) as mnt1, zfs_mount(dst) as mnt2:
|
||
|
self.assertTrue(
|
||
|
filecmp.cmp(
|
||
|
os.path.join(mnt1, name), os.path.join(mnt2, name), False))
|
||
|
|
||
|
def test_recv_incremental(self):
|
||
|
src1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
src2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
dst1 = ZFSTest.pool.makeName(b"fs2/received-2@snap1")
|
||
|
dst2 = ZFSTest.pool.makeName(b"fs2/received-2@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([src1])
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")) as name:
|
||
|
lzc.lzc_snapshot([src2])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src1, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(dst1, stream.fileno())
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src2, src1, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(dst2, stream.fileno())
|
||
|
|
||
|
name = os.path.basename(name)
|
||
|
with zfs_mount(src2) as mnt1, zfs_mount(dst2) as mnt2:
|
||
|
self.assertTrue(
|
||
|
filecmp.cmp(
|
||
|
os.path.join(mnt1, name), os.path.join(mnt2, name), False))
|
||
|
|
||
|
# This test case fails unless unless a patch from
|
||
|
# https://clusterhq.atlassian.net/browse/ZFS-20
|
||
|
# is applied to libzfs_core, otherwise it succeeds.
|
||
|
@unittest.skip("fails with unpatched libzfs_core")
|
||
|
def test_recv_without_explicit_snap_name(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-100")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dstfs, full.fileno())
|
||
|
lzc.lzc_receive(dstfs, incr.fileno())
|
||
|
self.assertExists(dst1)
|
||
|
self.assertExists(dst2)
|
||
|
|
||
|
def test_recv_clone(self):
|
||
|
orig_src = ZFSTest.pool.makeName(b"fs2@send-origin")
|
||
|
clone = ZFSTest.pool.makeName(b"fs1/fs/send-clone")
|
||
|
clone_snap = clone + b"@snap"
|
||
|
orig_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-origin@snap")
|
||
|
clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone@snap")
|
||
|
|
||
|
lzc.lzc_snapshot([orig_src])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(orig_src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(orig_dst, stream.fileno())
|
||
|
|
||
|
lzc.lzc_clone(clone, orig_src)
|
||
|
lzc.lzc_snapshot([clone_snap])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(clone_snap, orig_src, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(clone_dst, stream.fileno(), origin=orig_dst)
|
||
|
|
||
|
def test_recv_full_already_existing_empty_fs(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-3")
|
||
|
dst = dstfs + b'@snap'
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
|
||
|
lzc.lzc_snapshot([src])
|
||
|
lzc.lzc_create(dstfs)
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises((
|
||
|
lzc_exc.DestinationModified, lzc_exc.DatasetExists)):
|
||
|
lzc.lzc_receive(dst, stream.fileno())
|
||
|
|
||
|
def test_recv_full_into_root_empty_pool(self):
|
||
|
empty_pool = None
|
||
|
try:
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
empty_pool = _TempPool()
|
||
|
dst = empty_pool.makeName(b'@snap')
|
||
|
|
||
|
with streams(srcfs, b"snap", None) as (_, (stream, _)):
|
||
|
with self.assertRaises((
|
||
|
lzc_exc.DestinationModified, lzc_exc.DatasetExists)):
|
||
|
lzc.lzc_receive(dst, stream.fileno())
|
||
|
finally:
|
||
|
if empty_pool is not None:
|
||
|
empty_pool.cleanUp()
|
||
|
|
||
|
def test_recv_full_into_ro_pool(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
dst = ZFSTest.readonly_pool.makeName(b'fs2/received@snap')
|
||
|
|
||
|
with streams(srcfs, b"snap", None) as (_, (stream, _)):
|
||
|
with self.assertRaises(lzc_exc.ReadOnlyPool):
|
||
|
lzc.lzc_receive(dst, stream.fileno())
|
||
|
|
||
|
def test_recv_full_already_existing_modified_fs(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-5")
|
||
|
dst = dstfs + b'@snap'
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
|
||
|
lzc.lzc_snapshot([src])
|
||
|
lzc.lzc_create(dstfs)
|
||
|
with temp_file_in_fs(dstfs):
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises((
|
||
|
lzc_exc.DestinationModified, lzc_exc.DatasetExists)):
|
||
|
lzc.lzc_receive(dst, stream.fileno())
|
||
|
|
||
|
def test_recv_full_already_existing_with_snapshots(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-4")
|
||
|
dst = dstfs + b'@snap'
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
|
||
|
lzc.lzc_snapshot([src])
|
||
|
lzc.lzc_create(dstfs)
|
||
|
lzc.lzc_snapshot([dstfs + b"@snap1"])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises((
|
||
|
lzc_exc.StreamMismatch, lzc_exc.DatasetExists)):
|
||
|
lzc.lzc_receive(dst, stream.fileno())
|
||
|
|
||
|
def test_recv_full_already_existing_snapshot(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-6")
|
||
|
dst = dstfs + b'@snap'
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
|
||
|
lzc.lzc_snapshot([src])
|
||
|
lzc.lzc_create(dstfs)
|
||
|
lzc.lzc_snapshot([dst])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.DatasetExists):
|
||
|
lzc.lzc_receive(dst, stream.fileno())
|
||
|
|
||
|
def test_recv_full_missing_parent_fs(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dst = ZFSTest.pool.makeName(b"fs2/nonexistent/fs@snap")
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
|
||
|
lzc.lzc_snapshot([src])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.DatasetNotFound):
|
||
|
lzc.lzc_receive(dst, stream.fileno())
|
||
|
|
||
|
def test_recv_full_but_specify_origin(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src = srcfs + b"@snap"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-30")
|
||
|
dst = dstfs + b'@snap'
|
||
|
origin1 = ZFSTest.pool.makeName(b"fs2@snap1")
|
||
|
origin2 = ZFSTest.pool.makeName(b"fs2@snap2")
|
||
|
|
||
|
lzc.lzc_snapshot([origin1])
|
||
|
with streams(srcfs, src, None) as (_, (stream, _)):
|
||
|
lzc.lzc_receive(dst, stream.fileno(), origin=origin1)
|
||
|
origin = ZFSTest.pool.getFilesystem(
|
||
|
b"fs2/received-30").getProperty('origin')
|
||
|
self.assertEqual(origin, origin1)
|
||
|
stream.seek(0)
|
||
|
# because origin snap does not exist can't receive as a clone of it
|
||
|
with self.assertRaises((
|
||
|
lzc_exc.DatasetNotFound,
|
||
|
lzc_exc.BadStream)):
|
||
|
lzc.lzc_receive(dst, stream.fileno(), origin=origin2)
|
||
|
|
||
|
def test_recv_full_existing_empty_fs_and_origin(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src = srcfs + b"@snap"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-31")
|
||
|
dst = dstfs + b'@snap'
|
||
|
origin = dstfs + b'@dummy'
|
||
|
|
||
|
lzc.lzc_create(dstfs)
|
||
|
with streams(srcfs, src, None) as (_, (stream, _)):
|
||
|
# because the destination fs already exists and has no snaps
|
||
|
with self.assertRaises((
|
||
|
lzc_exc.DestinationModified,
|
||
|
lzc_exc.DatasetExists,
|
||
|
lzc_exc.BadStream)):
|
||
|
lzc.lzc_receive(dst, stream.fileno(), origin=origin)
|
||
|
lzc.lzc_snapshot([origin])
|
||
|
stream.seek(0)
|
||
|
# because the destination fs already exists and has the snap
|
||
|
with self.assertRaises((
|
||
|
lzc_exc.StreamMismatch,
|
||
|
lzc_exc.DatasetExists,
|
||
|
lzc_exc.BadStream)):
|
||
|
lzc.lzc_receive(dst, stream.fileno(), origin=origin)
|
||
|
|
||
|
def test_recv_incremental_mounted_fs(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-7")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
with zfs_mount(dstfs):
|
||
|
lzc.lzc_receive(dst2, incr.fileno())
|
||
|
|
||
|
def test_recv_incremental_modified_fs(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-15")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
with temp_file_in_fs(dstfs):
|
||
|
with self.assertRaises(lzc_exc.DestinationModified):
|
||
|
lzc.lzc_receive(dst2, incr.fileno())
|
||
|
|
||
|
def test_recv_incremental_snapname_used(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-8")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
lzc.lzc_snapshot([dst2])
|
||
|
with self.assertRaises(lzc_exc.DatasetExists):
|
||
|
lzc.lzc_receive(dst2, incr.fileno())
|
||
|
|
||
|
def test_recv_incremental_more_recent_snap_with_no_changes(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-9")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
dst_snap = dstfs + b'@snap'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
lzc.lzc_snapshot([dst_snap])
|
||
|
lzc.lzc_receive(dst2, incr.fileno())
|
||
|
|
||
|
def test_recv_incremental_non_clone_but_set_origin(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-20")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
dst_snap = dstfs + b'@snap'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
lzc.lzc_snapshot([dst_snap])
|
||
|
# becase cannot receive incremental and set origin on a non-clone
|
||
|
with self.assertRaises(lzc_exc.BadStream):
|
||
|
lzc.lzc_receive(dst2, incr.fileno(), origin=dst1)
|
||
|
|
||
|
def test_recv_incremental_non_clone_but_set_random_origin(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-21")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
dst_snap = dstfs + b'@snap'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
lzc.lzc_snapshot([dst_snap])
|
||
|
# because origin snap does not exist can't receive as a clone of it
|
||
|
with self.assertRaises((
|
||
|
lzc_exc.DatasetNotFound,
|
||
|
lzc_exc.BadStream)):
|
||
|
lzc.lzc_receive(
|
||
|
dst2, incr.fileno(),
|
||
|
origin=ZFSTest.pool.makeName(b"fs2/fs@snap"))
|
||
|
|
||
|
def test_recv_incremental_more_recent_snap(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-10")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
dst_snap = dstfs + b'@snap'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
with temp_file_in_fs(dstfs):
|
||
|
lzc.lzc_snapshot([dst_snap])
|
||
|
with self.assertRaises(lzc_exc.DestinationModified):
|
||
|
lzc.lzc_receive(dst2, incr.fileno())
|
||
|
|
||
|
def test_recv_incremental_duplicate(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-11")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
dst_snap = dstfs + b'@snap'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
lzc.lzc_receive(dst2, incr.fileno())
|
||
|
incr.seek(0)
|
||
|
with self.assertRaises(lzc_exc.DestinationModified):
|
||
|
lzc.lzc_receive(dst_snap, incr.fileno())
|
||
|
|
||
|
def test_recv_incremental_unrelated_fs(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-12")
|
||
|
dst_snap = dstfs + b'@snap'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (_, incr)):
|
||
|
lzc.lzc_create(dstfs)
|
||
|
with self.assertRaises(lzc_exc.StreamMismatch):
|
||
|
lzc.lzc_receive(dst_snap, incr.fileno())
|
||
|
|
||
|
def test_recv_incremental_nonexistent_fs(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-13")
|
||
|
dst_snap = dstfs + b'@snap'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (_, incr)):
|
||
|
with self.assertRaises(lzc_exc.DatasetNotFound):
|
||
|
lzc.lzc_receive(dst_snap, incr.fileno())
|
||
|
|
||
|
def test_recv_incremental_same_fs(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
src_snap = srcfs + b'@snap'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (_, incr)):
|
||
|
with self.assertRaises(lzc_exc.DestinationModified):
|
||
|
lzc.lzc_receive(src_snap, incr.fileno())
|
||
|
|
||
|
def test_recv_clone_without_specifying_origin(self):
|
||
|
orig_src = ZFSTest.pool.makeName(b"fs2@send-origin-2")
|
||
|
clone = ZFSTest.pool.makeName(b"fs1/fs/send-clone-2")
|
||
|
clone_snap = clone + b"@snap"
|
||
|
orig_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-origin-2@snap")
|
||
|
clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone-2@snap")
|
||
|
|
||
|
lzc.lzc_snapshot([orig_src])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(orig_src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(orig_dst, stream.fileno())
|
||
|
|
||
|
lzc.lzc_clone(clone, orig_src)
|
||
|
lzc.lzc_snapshot([clone_snap])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(clone_snap, orig_src, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.BadStream):
|
||
|
lzc.lzc_receive(clone_dst, stream.fileno())
|
||
|
|
||
|
def test_recv_clone_invalid_origin(self):
|
||
|
orig_src = ZFSTest.pool.makeName(b"fs2@send-origin-3")
|
||
|
clone = ZFSTest.pool.makeName(b"fs1/fs/send-clone-3")
|
||
|
clone_snap = clone + b"@snap"
|
||
|
orig_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-origin-3@snap")
|
||
|
clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone-3@snap")
|
||
|
|
||
|
lzc.lzc_snapshot([orig_src])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(orig_src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(orig_dst, stream.fileno())
|
||
|
|
||
|
lzc.lzc_clone(clone, orig_src)
|
||
|
lzc.lzc_snapshot([clone_snap])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(clone_snap, orig_src, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_receive(
|
||
|
clone_dst, stream.fileno(),
|
||
|
origin=ZFSTest.pool.makeName(b"fs1/fs"))
|
||
|
|
||
|
def test_recv_clone_wrong_origin(self):
|
||
|
orig_src = ZFSTest.pool.makeName(b"fs2@send-origin-4")
|
||
|
clone = ZFSTest.pool.makeName(b"fs1/fs/send-clone-4")
|
||
|
clone_snap = clone + b"@snap"
|
||
|
orig_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-origin-4@snap")
|
||
|
clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone-4@snap")
|
||
|
wrong_origin = ZFSTest.pool.makeName(b"fs1/fs@snap")
|
||
|
|
||
|
lzc.lzc_snapshot([orig_src])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(orig_src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(orig_dst, stream.fileno())
|
||
|
|
||
|
lzc.lzc_clone(clone, orig_src)
|
||
|
lzc.lzc_snapshot([clone_snap])
|
||
|
lzc.lzc_snapshot([wrong_origin])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(clone_snap, orig_src, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.StreamMismatch):
|
||
|
lzc.lzc_receive(
|
||
|
clone_dst, stream.fileno(), origin=wrong_origin)
|
||
|
|
||
|
def test_recv_clone_nonexistent_origin(self):
|
||
|
orig_src = ZFSTest.pool.makeName(b"fs2@send-origin-5")
|
||
|
clone = ZFSTest.pool.makeName(b"fs1/fs/send-clone-5")
|
||
|
clone_snap = clone + b"@snap"
|
||
|
orig_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-origin-5@snap")
|
||
|
clone_dst = ZFSTest.pool.makeName(b"fs1/fs/recv-clone-5@snap")
|
||
|
wrong_origin = ZFSTest.pool.makeName(b"fs1/fs@snap")
|
||
|
|
||
|
lzc.lzc_snapshot([orig_src])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(orig_src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(orig_dst, stream.fileno())
|
||
|
|
||
|
lzc.lzc_clone(clone, orig_src)
|
||
|
lzc.lzc_snapshot([clone_snap])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(clone_snap, orig_src, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.DatasetNotFound):
|
||
|
lzc.lzc_receive(
|
||
|
clone_dst, stream.fileno(), origin=wrong_origin)
|
||
|
|
||
|
def test_force_recv_full_existing_fs(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-50")
|
||
|
dst = dstfs + b'@snap'
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
|
||
|
lzc.lzc_snapshot([src])
|
||
|
|
||
|
lzc.lzc_create(dstfs)
|
||
|
with temp_file_in_fs(dstfs):
|
||
|
pass # enough to taint the fs
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(dst, stream.fileno(), force=True)
|
||
|
|
||
|
def test_force_recv_full_existing_modified_mounted_fs(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-53")
|
||
|
dst = dstfs + b'@snap'
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
|
||
|
lzc.lzc_snapshot([src])
|
||
|
|
||
|
lzc.lzc_create(dstfs)
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with zfs_mount(dstfs) as mntdir:
|
||
|
f = tempfile.NamedTemporaryFile(dir=mntdir, delete=False)
|
||
|
for i in range(1024):
|
||
|
f.write(b'x' * 1024)
|
||
|
lzc.lzc_receive(dst, stream.fileno(), force=True)
|
||
|
# The temporary file dissappears and any access, even close(),
|
||
|
# results in EIO.
|
||
|
self.assertFalse(os.path.exists(f.name))
|
||
|
with self.assertRaises(IOError):
|
||
|
f.close()
|
||
|
|
||
|
# This test-case expects the behavior that should be there,
|
||
|
# at the moment it may fail with DatasetExists or StreamMismatch
|
||
|
# depending on the implementation.
|
||
|
def test_force_recv_full_already_existing_with_snapshots(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-51")
|
||
|
dst = dstfs + b'@snap'
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
|
||
|
lzc.lzc_snapshot([src])
|
||
|
|
||
|
lzc.lzc_create(dstfs)
|
||
|
with temp_file_in_fs(dstfs):
|
||
|
pass # enough to taint the fs
|
||
|
lzc.lzc_snapshot([dstfs + b"@snap1"])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(dst, stream.fileno(), force=True)
|
||
|
|
||
|
def test_force_recv_full_already_existing_with_same_snap(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-52")
|
||
|
dst = dstfs + b'@snap'
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
|
||
|
lzc.lzc_snapshot([src])
|
||
|
|
||
|
lzc.lzc_create(dstfs)
|
||
|
with temp_file_in_fs(dstfs):
|
||
|
pass # enough to taint the fs
|
||
|
lzc.lzc_snapshot([dst])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.DatasetExists):
|
||
|
lzc.lzc_receive(dst, stream.fileno(), force=True)
|
||
|
|
||
|
def test_force_recv_full_missing_parent_fs(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dst = ZFSTest.pool.makeName(b"fs2/nonexistent/fs@snap")
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")):
|
||
|
lzc.lzc_snapshot([src])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.DatasetNotFound):
|
||
|
lzc.lzc_receive(dst, stream.fileno(), force=True)
|
||
|
|
||
|
def test_force_recv_incremental_modified_fs(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-60")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
with temp_file_in_fs(dstfs):
|
||
|
pass # enough to taint the fs
|
||
|
lzc.lzc_receive(dst2, incr.fileno(), force=True)
|
||
|
|
||
|
def test_force_recv_incremental_modified_mounted_fs(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-64")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
with zfs_mount(dstfs) as mntdir:
|
||
|
f = tempfile.NamedTemporaryFile(dir=mntdir, delete=False)
|
||
|
for i in range(1024):
|
||
|
f.write(b'x' * 1024)
|
||
|
lzc.lzc_receive(dst2, incr.fileno(), force=True)
|
||
|
# The temporary file dissappears and any access, even close(),
|
||
|
# results in EIO.
|
||
|
self.assertFalse(os.path.exists(f.name))
|
||
|
with self.assertRaises(IOError):
|
||
|
f.close()
|
||
|
|
||
|
def test_force_recv_incremental_modified_fs_plus_later_snap(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-61")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
dst3 = dstfs + b'@snap'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
with temp_file_in_fs(dstfs):
|
||
|
pass # enough to taint the fs
|
||
|
lzc.lzc_snapshot([dst3])
|
||
|
lzc.lzc_receive(dst2, incr.fileno(), force=True)
|
||
|
self.assertExists(dst1)
|
||
|
self.assertExists(dst2)
|
||
|
self.assertNotExists(dst3)
|
||
|
|
||
|
def test_force_recv_incremental_modified_fs_plus_same_name_snap(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-62")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
with temp_file_in_fs(dstfs):
|
||
|
pass # enough to taint the fs
|
||
|
lzc.lzc_snapshot([dst2])
|
||
|
with self.assertRaises(lzc_exc.DatasetExists):
|
||
|
lzc.lzc_receive(dst2, incr.fileno(), force=True)
|
||
|
|
||
|
def test_force_recv_incremental_modified_fs_plus_held_snap(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-63")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
dst3 = dstfs + b'@snap'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
with temp_file_in_fs(dstfs):
|
||
|
pass # enough to taint the fs
|
||
|
lzc.lzc_snapshot([dst3])
|
||
|
with cleanup_fd() as cfd:
|
||
|
lzc.lzc_hold({dst3: b'tag'}, cfd)
|
||
|
with self.assertRaises(lzc_exc.DatasetBusy):
|
||
|
lzc.lzc_receive(dst2, incr.fileno(), force=True)
|
||
|
self.assertExists(dst1)
|
||
|
self.assertNotExists(dst2)
|
||
|
self.assertExists(dst3)
|
||
|
|
||
|
def test_force_recv_incremental_modified_fs_plus_cloned_snap(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-70")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
dst2 = dstfs + b'@snap2'
|
||
|
dst3 = dstfs + b'@snap'
|
||
|
cloned = ZFSTest.pool.makeName(b"fs2/received-cloned-70")
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
with temp_file_in_fs(dstfs):
|
||
|
pass # enough to taint the fs
|
||
|
lzc.lzc_snapshot([dst3])
|
||
|
lzc.lzc_clone(cloned, dst3)
|
||
|
with self.assertRaises(lzc_exc.DatasetExists):
|
||
|
lzc.lzc_receive(dst2, incr.fileno(), force=True)
|
||
|
self.assertExists(dst1)
|
||
|
self.assertNotExists(dst2)
|
||
|
self.assertExists(dst3)
|
||
|
|
||
|
def test_recv_incremental_into_cloned_fs(self):
|
||
|
srcfs = ZFSTest.pool.makeName(b"fs1")
|
||
|
src1 = srcfs + b"@snap1"
|
||
|
src2 = srcfs + b"@snap2"
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received-71")
|
||
|
dst1 = dstfs + b'@snap1'
|
||
|
cloned = ZFSTest.pool.makeName(b"fs2/received-cloned-71")
|
||
|
dst2 = cloned + b'@snap'
|
||
|
|
||
|
with streams(srcfs, src1, src2) as (_, (full, incr)):
|
||
|
lzc.lzc_receive(dst1, full.fileno())
|
||
|
lzc.lzc_clone(cloned, dst1)
|
||
|
# test both graceful and with-force attempts
|
||
|
with self.assertRaises(lzc_exc.StreamMismatch):
|
||
|
lzc.lzc_receive(dst2, incr.fileno())
|
||
|
incr.seek(0)
|
||
|
with self.assertRaises(lzc_exc.StreamMismatch):
|
||
|
lzc.lzc_receive(dst2, incr.fileno(), force=True)
|
||
|
self.assertExists(dst1)
|
||
|
self.assertNotExists(dst2)
|
||
|
|
||
|
def test_recv_with_header_full(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dst = ZFSTest.pool.makeName(b"fs2/received")
|
||
|
|
||
|
with temp_file_in_fs(ZFSTest.pool.makeName(b"fs1")) as name:
|
||
|
lzc.lzc_snapshot([src])
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
|
||
|
(header, c_header) = lzc.receive_header(stream.fileno())
|
||
|
self.assertEqual(src, header['drr_toname'])
|
||
|
snap = header['drr_toname'].split(b'@', 1)[1]
|
||
|
lzc.lzc_receive_with_header(
|
||
|
dst + b'@' + snap, stream.fileno(), c_header)
|
||
|
|
||
|
name = os.path.basename(name)
|
||
|
with zfs_mount(src) as mnt1, zfs_mount(dst) as mnt2:
|
||
|
self.assertTrue(
|
||
|
filecmp.cmp(
|
||
|
os.path.join(mnt1, name), os.path.join(mnt2, name), False))
|
||
|
|
||
|
def test_recv_fs_below_zvol(self):
|
||
|
send = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
zvol = ZFSTest.pool.makeName(b"fs1/zvol")
|
||
|
dest = zvol + b"/fs@snap"
|
||
|
props = {b"volsize": 1024 * 1024}
|
||
|
|
||
|
lzc.lzc_snapshot([send])
|
||
|
lzc.lzc_create(zvol, ds_type='zvol', props=props)
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(send, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.WrongParent):
|
||
|
lzc.lzc_receive(dest, stream.fileno())
|
||
|
|
||
|
def test_recv_zvol_over_fs_with_children(self):
|
||
|
parent = ZFSTest.pool.makeName(b"fs1")
|
||
|
child = parent + b"subfs"
|
||
|
zvol = ZFSTest.pool.makeName(b"fs1/zvol")
|
||
|
send = zvol + b"@snap"
|
||
|
props = {b"volsize": 1024 * 1024}
|
||
|
|
||
|
lzc.lzc_create(child)
|
||
|
lzc.lzc_create(zvol, ds_type='zvol', props=props)
|
||
|
lzc.lzc_snapshot([send])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(send, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.WrongParent):
|
||
|
lzc.lzc_receive(parent + b"@snap", stream.fileno(), force=True)
|
||
|
|
||
|
def test_recv_zvol_overwrite_rootds(self):
|
||
|
zvol = ZFSTest.pool.makeName(b"fs1/zvol")
|
||
|
snap = zvol + b"@snap"
|
||
|
rootds = ZFSTest.pool.getRoot().getName()
|
||
|
props = {b"volsize": 1024 * 1024}
|
||
|
|
||
|
lzc.lzc_create(zvol, ds_type='zvol', props=props)
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(snap, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.WrongParent):
|
||
|
lzc.lzc_receive(rootds + b"@snap", stream.fileno(), force=True)
|
||
|
|
||
|
def test_send_full_across_clone_branch_point(self):
|
||
|
origfs = ZFSTest.pool.makeName(b"fs2")
|
||
|
|
||
|
(_, (fromsnap, origsnap, _)) = make_snapshots(
|
||
|
origfs, b"snap1", b"send-origin-20", None)
|
||
|
|
||
|
clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-20")
|
||
|
lzc.lzc_clone(clonefs, origsnap)
|
||
|
|
||
|
(_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(tosnap, None, stream.fileno())
|
||
|
|
||
|
def test_send_incr_across_clone_branch_point(self):
|
||
|
origfs = ZFSTest.pool.makeName(b"fs2")
|
||
|
|
||
|
(_, (fromsnap, origsnap, _)) = make_snapshots(
|
||
|
origfs, b"snap1", b"send-origin-21", None)
|
||
|
|
||
|
clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-21")
|
||
|
lzc.lzc_clone(clonefs, origsnap)
|
||
|
|
||
|
(_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
|
||
|
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(tosnap, fromsnap, stream.fileno())
|
||
|
|
||
|
def test_send_resume_token_full(self):
|
||
|
src = ZFSTest.pool.makeName(b"fs1@snap")
|
||
|
dstfs = ZFSTest.pool.getFilesystem(b"fs2/received")
|
||
|
dst = dstfs.getSnap()
|
||
|
|
||
|
with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
|
||
|
for i in range(1, 10):
|
||
|
with tempfile.NamedTemporaryFile(dir=mntdir) as f:
|
||
|
f.write(b'x' * 1024 * i)
|
||
|
f.flush()
|
||
|
lzc.lzc_snapshot([src])
|
||
|
|
||
|
with tempfile.NamedTemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(src, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
stream.truncate(1024 * 3)
|
||
|
with self.assertRaises(lzc_exc.BadStream):
|
||
|
lzc.lzc_receive_resumable(dst, stream.fileno())
|
||
|
# Resume token code from zfs_send_resume_token_to_nvlist()
|
||
|
# XXX: if used more than twice move this code into an external func
|
||
|
# format: <version>-<cksum>-<packed-size>-<compressed-payload>
|
||
|
token = dstfs.getProperty("receive_resume_token")
|
||
|
self.assertNotEqual(token, b'-')
|
||
|
tokens = token.split(b'-')
|
||
|
self.assertEqual(len(tokens), 4)
|
||
|
version = tokens[0]
|
||
|
packed_size = int(tokens[2], 16)
|
||
|
compressed_nvs = tokens[3]
|
||
|
# Validate resume token
|
||
|
self.assertEqual(version, b'1') # ZFS_SEND_RESUME_TOKEN_VERSION
|
||
|
if sys.version_info < (3, 0):
|
||
|
payload = (
|
||
|
zlib.decompress(str(bytearray.fromhex(compressed_nvs)))
|
||
|
)
|
||
|
else:
|
||
|
payload = (
|
||
|
zlib.decompress(bytearray.fromhex(compressed_nvs.decode()))
|
||
|
)
|
||
|
self.assertEqual(len(payload), packed_size)
|
||
|
# Unpack
|
||
|
resume_values = packed_nvlist_out(payload, packed_size)
|
||
|
resumeobj = resume_values.get(b'object')
|
||
|
resumeoff = resume_values.get(b'offset')
|
||
|
with tempfile.NamedTemporaryFile(suffix='.zstream') as rstream:
|
||
|
lzc.lzc_send_resume(
|
||
|
src, None, rstream.fileno(), None, resumeobj, resumeoff)
|
||
|
rstream.seek(0)
|
||
|
lzc.lzc_receive_resumable(dst, rstream.fileno())
|
||
|
|
||
|
def test_send_resume_token_incremental(self):
|
||
|
snap1 = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
snap2 = ZFSTest.pool.makeName(b"fs1@snap2")
|
||
|
dstfs = ZFSTest.pool.getFilesystem(b"fs2/received")
|
||
|
dst1 = dstfs.getSnap()
|
||
|
dst2 = dstfs.getSnap()
|
||
|
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
with tempfile.NamedTemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(snap1, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(dst1, stream.fileno())
|
||
|
|
||
|
with zfs_mount(ZFSTest.pool.makeName(b"fs1")) as mntdir:
|
||
|
for i in range(1, 10):
|
||
|
with tempfile.NamedTemporaryFile(dir=mntdir) as f:
|
||
|
f.write(b'x' * 1024 * i)
|
||
|
f.flush()
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with tempfile.NamedTemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(snap2, snap1, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
stream.truncate(1024 * 3)
|
||
|
with self.assertRaises(lzc_exc.BadStream):
|
||
|
lzc.lzc_receive_resumable(dst2, stream.fileno())
|
||
|
# Resume token code from zfs_send_resume_token_to_nvlist()
|
||
|
# format: <version>-<cksum>-<packed-size>-<compressed-payload>
|
||
|
token = dstfs.getProperty("receive_resume_token")
|
||
|
self.assertNotEqual(token, '-')
|
||
|
tokens = token.split(b'-')
|
||
|
self.assertEqual(len(tokens), 4)
|
||
|
version = tokens[0]
|
||
|
packed_size = int(tokens[2], 16)
|
||
|
compressed_nvs = tokens[3]
|
||
|
# Validate resume token
|
||
|
self.assertEqual(version, b'1') # ZFS_SEND_RESUME_TOKEN_VERSION
|
||
|
if sys.version_info < (3, 0):
|
||
|
payload = (
|
||
|
zlib.decompress(str(bytearray.fromhex(compressed_nvs)))
|
||
|
)
|
||
|
else:
|
||
|
payload = (
|
||
|
zlib.decompress(bytearray.fromhex(compressed_nvs.decode()))
|
||
|
)
|
||
|
self.assertEqual(len(payload), packed_size)
|
||
|
# Unpack
|
||
|
resume_values = packed_nvlist_out(payload, packed_size)
|
||
|
resumeobj = resume_values.get(b'object')
|
||
|
resumeoff = resume_values.get(b'offset')
|
||
|
with tempfile.NamedTemporaryFile(suffix='.zstream') as rstream:
|
||
|
lzc.lzc_send_resume(
|
||
|
snap2, snap1, rstream.fileno(), None, resumeobj, resumeoff)
|
||
|
rstream.seek(0)
|
||
|
lzc.lzc_receive_resumable(dst2, rstream.fileno())
|
||
|
|
||
|
def test_recv_full_across_clone_branch_point(self):
|
||
|
origfs = ZFSTest.pool.makeName(b"fs2")
|
||
|
|
||
|
(_, (fromsnap, origsnap, _)) = make_snapshots(
|
||
|
origfs, b"snap1", b"send-origin-30", None)
|
||
|
|
||
|
clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-30")
|
||
|
lzc.lzc_clone(clonefs, origsnap)
|
||
|
|
||
|
(_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
|
||
|
|
||
|
recvfs = ZFSTest.pool.makeName(b"fs1/recv-clone-30")
|
||
|
recvsnap = recvfs + b"@snap"
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(tosnap, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(recvsnap, stream.fileno())
|
||
|
|
||
|
def test_recv_one(self):
|
||
|
fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
tosnap = ZFSTest.pool.makeName(b"recv@snap1")
|
||
|
|
||
|
lzc.lzc_snapshot([fromsnap])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(fromsnap, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
(header, c_header) = lzc.receive_header(stream.fileno())
|
||
|
lzc.lzc_receive_one(tosnap, stream.fileno(), c_header)
|
||
|
|
||
|
def test_recv_one_size(self):
|
||
|
fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
tosnap = ZFSTest.pool.makeName(b"recv@snap1")
|
||
|
|
||
|
lzc.lzc_snapshot([fromsnap])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(fromsnap, None, stream.fileno())
|
||
|
size = os.fstat(stream.fileno()).st_size
|
||
|
stream.seek(0)
|
||
|
(header, c_header) = lzc.receive_header(stream.fileno())
|
||
|
(read, _) = lzc.lzc_receive_one(tosnap, stream.fileno(), c_header)
|
||
|
self.assertAlmostEqual(read, size, delta=read * 0.05)
|
||
|
|
||
|
def test_recv_one_props(self):
|
||
|
fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
fs = ZFSTest.pool.getFilesystem(b"recv")
|
||
|
tosnap = fs.getName() + b"@snap1"
|
||
|
props = {
|
||
|
b"compression": 0x01,
|
||
|
b"ns:prop": b"val"
|
||
|
}
|
||
|
|
||
|
lzc.lzc_snapshot([fromsnap])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(fromsnap, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
(header, c_header) = lzc.receive_header(stream.fileno())
|
||
|
lzc.lzc_receive_one(tosnap, stream.fileno(), c_header, props=props)
|
||
|
self.assertExists(tosnap)
|
||
|
self.assertEqual(fs.getProperty("compression", "received"), b"on")
|
||
|
self.assertEqual(fs.getProperty("ns:prop", "received"), b"val")
|
||
|
|
||
|
def test_recv_one_invalid_prop(self):
|
||
|
fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
fs = ZFSTest.pool.getFilesystem(b"recv")
|
||
|
tosnap = fs.getName() + b"@snap1"
|
||
|
props = {
|
||
|
b"exec": 0xff,
|
||
|
b"atime": 0x00
|
||
|
}
|
||
|
|
||
|
lzc.lzc_snapshot([fromsnap])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(fromsnap, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
(header, c_header) = lzc.receive_header(stream.fileno())
|
||
|
with self.assertRaises(lzc_exc.ReceivePropertyFailure) as ctx:
|
||
|
lzc.lzc_receive_one(
|
||
|
tosnap, stream.fileno(), c_header, props=props)
|
||
|
self.assertExists(tosnap)
|
||
|
self.assertEqual(fs.getProperty("atime", "received"), b"off")
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.PropertyInvalid)
|
||
|
self.assertEqual(e.name, b"exec")
|
||
|
|
||
|
def test_recv_with_cmdprops(self):
|
||
|
fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
fs = ZFSTest.pool.getFilesystem(b"recv")
|
||
|
tosnap = fs.getName() + b"@snap1"
|
||
|
props = {}
|
||
|
cmdprops = {
|
||
|
b"compression": 0x01,
|
||
|
b"ns:prop": b"val"
|
||
|
}
|
||
|
|
||
|
lzc.lzc_snapshot([fromsnap])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(fromsnap, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
(header, c_header) = lzc.receive_header(stream.fileno())
|
||
|
lzc.lzc_receive_with_cmdprops(
|
||
|
tosnap, stream.fileno(), c_header, props=props,
|
||
|
cmdprops=cmdprops)
|
||
|
self.assertExists(tosnap)
|
||
|
self.assertEqual(fs.getProperty("compression"), b"on")
|
||
|
self.assertEqual(fs.getProperty("ns:prop"), b"val")
|
||
|
|
||
|
def test_recv_with_cmdprops_and_recvprops(self):
|
||
|
fromsnap = ZFSTest.pool.makeName(b"fs1@snap1")
|
||
|
fs = ZFSTest.pool.getFilesystem(b"recv")
|
||
|
tosnap = fs.getName() + b"@snap1"
|
||
|
props = {
|
||
|
b"atime": 0x01,
|
||
|
b"exec": 0x00,
|
||
|
b"ns:prop": b"abc"
|
||
|
}
|
||
|
cmdprops = {
|
||
|
b"compression": 0x01,
|
||
|
b"ns:prop": b"def",
|
||
|
b"exec": None,
|
||
|
}
|
||
|
|
||
|
lzc.lzc_snapshot([fromsnap])
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(fromsnap, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
(header, c_header) = lzc.receive_header(stream.fileno())
|
||
|
lzc.lzc_receive_with_cmdprops(
|
||
|
tosnap, stream.fileno(), c_header, props=props,
|
||
|
cmdprops=cmdprops)
|
||
|
self.assertExists(tosnap)
|
||
|
self.assertEqual(fs.getProperty("atime", True), b"on")
|
||
|
self.assertEqual(fs.getProperty("exec", True), b"off")
|
||
|
self.assertEqual(fs.getProperty("ns:prop", True), b"abc")
|
||
|
self.assertEqual(fs.getProperty("compression"), b"on")
|
||
|
self.assertEqual(fs.getProperty("ns:prop"), b"def")
|
||
|
self.assertEqual(fs.getProperty("exec"), b"on")
|
||
|
|
||
|
def test_recv_incr_across_clone_branch_point_no_origin(self):
|
||
|
origfs = ZFSTest.pool.makeName(b"fs2")
|
||
|
|
||
|
(_, (fromsnap, origsnap, _)) = make_snapshots(
|
||
|
origfs, b"snap1", b"send-origin-32", None)
|
||
|
|
||
|
clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-32")
|
||
|
lzc.lzc_clone(clonefs, origsnap)
|
||
|
|
||
|
(_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
|
||
|
|
||
|
recvfs = ZFSTest.pool.makeName(b"fs1/recv-clone-32")
|
||
|
recvsnap1 = recvfs + b"@snap1"
|
||
|
recvsnap2 = recvfs + b"@snap2"
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(fromsnap, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(recvsnap1, stream.fileno())
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(tosnap, fromsnap, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.BadStream):
|
||
|
lzc.lzc_receive(recvsnap2, stream.fileno())
|
||
|
|
||
|
def test_recv_incr_across_clone_branch_point(self):
|
||
|
origfs = ZFSTest.pool.makeName(b"fs2")
|
||
|
|
||
|
(_, (fromsnap, origsnap, _)) = make_snapshots(
|
||
|
origfs, b"snap1", b"send-origin-31", None)
|
||
|
|
||
|
clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-31")
|
||
|
lzc.lzc_clone(clonefs, origsnap)
|
||
|
|
||
|
(_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
|
||
|
|
||
|
recvfs = ZFSTest.pool.makeName(b"fs1/recv-clone-31")
|
||
|
recvsnap1 = recvfs + b"@snap1"
|
||
|
recvsnap2 = recvfs + b"@snap2"
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(fromsnap, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(recvsnap1, stream.fileno())
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(tosnap, fromsnap, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
with self.assertRaises(lzc_exc.BadStream):
|
||
|
lzc.lzc_receive(recvsnap2, stream.fileno(), origin=recvsnap1)
|
||
|
|
||
|
def test_recv_incr_across_clone_branch_point_new_fs(self):
|
||
|
origfs = ZFSTest.pool.makeName(b"fs2")
|
||
|
|
||
|
(_, (fromsnap, origsnap, _)) = make_snapshots(
|
||
|
origfs, b"snap1", b"send-origin-33", None)
|
||
|
|
||
|
clonefs = ZFSTest.pool.makeName(b"fs1/fs/send-clone-33")
|
||
|
lzc.lzc_clone(clonefs, origsnap)
|
||
|
|
||
|
(_, (_, tosnap, _)) = make_snapshots(clonefs, None, b"snap", None)
|
||
|
|
||
|
recvfs1 = ZFSTest.pool.makeName(b"fs1/recv-clone-33")
|
||
|
recvsnap1 = recvfs1 + b"@snap"
|
||
|
recvfs2 = ZFSTest.pool.makeName(b"fs1/recv-clone-33_2")
|
||
|
recvsnap2 = recvfs2 + b"@snap"
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(fromsnap, None, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(recvsnap1, stream.fileno())
|
||
|
with tempfile.TemporaryFile(suffix='.zstream') as stream:
|
||
|
lzc.lzc_send(tosnap, fromsnap, stream.fileno())
|
||
|
stream.seek(0)
|
||
|
lzc.lzc_receive(recvsnap2, stream.fileno(), origin=recvsnap1)
|
||
|
|
||
|
def test_recv_bad_stream(self):
|
||
|
dstfs = ZFSTest.pool.makeName(b"fs2/received")
|
||
|
dst_snap = dstfs + b'@snap'
|
||
|
|
||
|
with dev_zero() as fd:
|
||
|
with self.assertRaises(lzc_exc.BadStream):
|
||
|
lzc.lzc_receive(dst_snap, fd)
|
||
|
|
||
|
@needs_support(lzc.lzc_promote)
|
||
|
def test_promote(self):
|
||
|
origfs = ZFSTest.pool.makeName(b"fs2")
|
||
|
snap = b"@promote-snap-1"
|
||
|
origsnap = origfs + snap
|
||
|
lzc.lzc_snap([origsnap])
|
||
|
|
||
|
clonefs = ZFSTest.pool.makeName(b"fs1/fs/promote-clone-1")
|
||
|
lzc.lzc_clone(clonefs, origsnap)
|
||
|
|
||
|
lzc.lzc_promote(clonefs)
|
||
|
# the snapshot now should belong to the promoted fs
|
||
|
self.assertExists(clonefs + snap)
|
||
|
|
||
|
@needs_support(lzc.lzc_promote)
|
||
|
def test_promote_too_long_snapname(self):
|
||
|
# origfs name must be shorter than clonefs name
|
||
|
origfs = ZFSTest.pool.makeName(b"fs2")
|
||
|
clonefs = ZFSTest.pool.makeName(b"fs1/fs/promote-clone-2")
|
||
|
snapprefix = b"@promote-snap-2-"
|
||
|
pad_len = 1 + lzc.MAXNAMELEN - len(clonefs) - len(snapprefix)
|
||
|
snap = snapprefix + b'x' * pad_len
|
||
|
origsnap = origfs + snap
|
||
|
|
||
|
lzc.lzc_snap([origsnap])
|
||
|
lzc.lzc_clone(clonefs, origsnap)
|
||
|
|
||
|
# This may fail on older buggy systems.
|
||
|
# See: https://www.illumos.org/issues/5909
|
||
|
with self.assertRaises(lzc_exc.NameTooLong):
|
||
|
lzc.lzc_promote(clonefs)
|
||
|
|
||
|
@needs_support(lzc.lzc_promote)
|
||
|
def test_promote_not_cloned(self):
|
||
|
fs = ZFSTest.pool.makeName(b"fs2")
|
||
|
with self.assertRaises(lzc_exc.NotClone):
|
||
|
lzc.lzc_promote(fs)
|
||
|
|
||
|
@unittest.skipIf(*illumos_bug_6379())
|
||
|
def test_hold_bad_fd(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with tempfile.TemporaryFile() as tmp:
|
||
|
bad_fd = tmp.fileno()
|
||
|
|
||
|
with self.assertRaises(lzc_exc.BadHoldCleanupFD):
|
||
|
lzc.lzc_hold({snap: b'tag'}, bad_fd)
|
||
|
|
||
|
@unittest.skipIf(*illumos_bug_6379())
|
||
|
def test_hold_bad_fd_2(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.BadHoldCleanupFD):
|
||
|
lzc.lzc_hold({snap: b'tag'}, -2)
|
||
|
|
||
|
@unittest.skipIf(*illumos_bug_6379())
|
||
|
def test_hold_bad_fd_3(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
(soft, hard) = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||
|
bad_fd = hard + 1
|
||
|
with self.assertRaises(lzc_exc.BadHoldCleanupFD):
|
||
|
lzc.lzc_hold({snap: b'tag'}, bad_fd)
|
||
|
|
||
|
@unittest.skipIf(*illumos_bug_6379())
|
||
|
def test_hold_wrong_fd(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with tempfile.TemporaryFile() as tmp:
|
||
|
fd = tmp.fileno()
|
||
|
with self.assertRaises(lzc_exc.BadHoldCleanupFD):
|
||
|
lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
|
||
|
def test_hold_fd(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
|
||
|
def test_hold_empty(self):
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({}, fd)
|
||
|
|
||
|
def test_hold_empty_2(self):
|
||
|
lzc.lzc_hold({})
|
||
|
|
||
|
def test_hold_vs_snap_destroy(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.SnapshotDestructionFailure) as ctx:
|
||
|
lzc.lzc_destroy_snaps([snap], defer=False)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.SnapshotIsHeld)
|
||
|
|
||
|
lzc.lzc_destroy_snaps([snap], defer=True)
|
||
|
self.assertExists(snap)
|
||
|
|
||
|
# after automatic hold cleanup and deferred destruction
|
||
|
self.assertNotExists(snap)
|
||
|
|
||
|
def test_hold_many_tags(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({snap: b'tag1'}, fd)
|
||
|
lzc.lzc_hold({snap: b'tag2'}, fd)
|
||
|
|
||
|
def test_hold_many_snaps(self):
|
||
|
snap1 = ZFSTest.pool.getRoot().getSnap()
|
||
|
snap2 = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({snap1: b'tag', snap2: b'tag'}, fd)
|
||
|
|
||
|
def test_hold_many_with_one_missing(self):
|
||
|
snap1 = ZFSTest.pool.getRoot().getSnap()
|
||
|
snap2 = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
missing = lzc.lzc_hold({snap1: b'tag', snap2: b'tag'}, fd)
|
||
|
self.assertEqual(len(missing), 1)
|
||
|
self.assertEqual(missing[0], snap2)
|
||
|
|
||
|
def test_hold_many_with_all_missing(self):
|
||
|
snap1 = ZFSTest.pool.getRoot().getSnap()
|
||
|
snap2 = ZFSTest.pool.getRoot().getSnap()
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
missing = lzc.lzc_hold({snap1: b'tag', snap2: b'tag'}, fd)
|
||
|
self.assertEqual(len(missing), 2)
|
||
|
self.assertEqual(sorted(missing), sorted([snap1, snap2]))
|
||
|
|
||
|
def test_hold_missing_fs(self):
|
||
|
# XXX skip pre-created filesystems
|
||
|
ZFSTest.pool.getRoot().getFilesystem()
|
||
|
ZFSTest.pool.getRoot().getFilesystem()
|
||
|
ZFSTest.pool.getRoot().getFilesystem()
|
||
|
ZFSTest.pool.getRoot().getFilesystem()
|
||
|
ZFSTest.pool.getRoot().getFilesystem()
|
||
|
snap = ZFSTest.pool.getRoot().getFilesystem().getSnap()
|
||
|
|
||
|
snaps = lzc.lzc_hold({snap: b'tag'})
|
||
|
self.assertEqual([snap], snaps)
|
||
|
|
||
|
def test_hold_missing_fs_auto_cleanup(self):
|
||
|
# XXX skip pre-created filesystems
|
||
|
ZFSTest.pool.getRoot().getFilesystem()
|
||
|
ZFSTest.pool.getRoot().getFilesystem()
|
||
|
ZFSTest.pool.getRoot().getFilesystem()
|
||
|
ZFSTest.pool.getRoot().getFilesystem()
|
||
|
ZFSTest.pool.getRoot().getFilesystem()
|
||
|
snap = ZFSTest.pool.getRoot().getFilesystem().getSnap()
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
snaps = lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
self.assertEqual([snap], snaps)
|
||
|
|
||
|
def test_hold_duplicate(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
with self.assertRaises(lzc_exc.HoldFailure) as ctx:
|
||
|
lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.HoldExists)
|
||
|
|
||
|
def test_hold_across_pools(self):
|
||
|
snap1 = ZFSTest.pool.getRoot().getSnap()
|
||
|
snap2 = ZFSTest.misc_pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
with self.assertRaises(lzc_exc.HoldFailure) as ctx:
|
||
|
lzc.lzc_hold({snap1: b'tag', snap2: b'tag'}, fd)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.PoolsDiffer)
|
||
|
|
||
|
def test_hold_too_long_tag(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
tag = b't' * 256
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
with self.assertRaises(lzc_exc.HoldFailure) as ctx:
|
||
|
lzc.lzc_hold({snap: tag}, fd)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameTooLong)
|
||
|
self.assertEqual(e.name, tag)
|
||
|
|
||
|
# Apparently the full snapshot name is not checked for length
|
||
|
# and this snapshot is treated as simply missing.
|
||
|
@unittest.expectedFailure
|
||
|
def test_hold_too_long_snap_name(self):
|
||
|
snap = ZFSTest.pool.getRoot().getTooLongSnap(False)
|
||
|
with cleanup_fd() as fd:
|
||
|
with self.assertRaises(lzc_exc.HoldFailure) as ctx:
|
||
|
lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameTooLong)
|
||
|
self.assertEqual(e.name, snap)
|
||
|
|
||
|
def test_hold_too_long_snap_name_2(self):
|
||
|
snap = ZFSTest.pool.getRoot().getTooLongSnap(True)
|
||
|
with cleanup_fd() as fd:
|
||
|
with self.assertRaises(lzc_exc.HoldFailure) as ctx:
|
||
|
lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameTooLong)
|
||
|
self.assertEqual(e.name, snap)
|
||
|
|
||
|
def test_hold_invalid_snap_name(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap() + b'@bad'
|
||
|
with cleanup_fd() as fd:
|
||
|
with self.assertRaises(lzc_exc.HoldFailure) as ctx:
|
||
|
lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameInvalid)
|
||
|
self.assertEqual(e.name, snap)
|
||
|
|
||
|
def test_hold_invalid_snap_name_2(self):
|
||
|
snap = ZFSTest.pool.getRoot().getFilesystem().getName()
|
||
|
with cleanup_fd() as fd:
|
||
|
with self.assertRaises(lzc_exc.HoldFailure) as ctx:
|
||
|
lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameInvalid)
|
||
|
self.assertEqual(e.name, snap)
|
||
|
|
||
|
def test_get_holds(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({snap: b'tag1'}, fd)
|
||
|
lzc.lzc_hold({snap: b'tag2'}, fd)
|
||
|
|
||
|
holds = lzc.lzc_get_holds(snap)
|
||
|
self.assertEqual(len(holds), 2)
|
||
|
self.assertIn(b'tag1', holds)
|
||
|
self.assertIn(b'tag2', holds)
|
||
|
self.assertIsInstance(holds[b'tag1'], (int, int))
|
||
|
|
||
|
def test_get_holds_after_auto_cleanup(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({snap: b'tag1'}, fd)
|
||
|
lzc.lzc_hold({snap: b'tag2'}, fd)
|
||
|
|
||
|
holds = lzc.lzc_get_holds(snap)
|
||
|
self.assertEqual(len(holds), 0)
|
||
|
self.assertIsInstance(holds, dict)
|
||
|
|
||
|
def test_get_holds_nonexistent_snap(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
with self.assertRaises(lzc_exc.SnapshotNotFound):
|
||
|
lzc.lzc_get_holds(snap)
|
||
|
|
||
|
def test_get_holds_too_long_snap_name(self):
|
||
|
snap = ZFSTest.pool.getRoot().getTooLongSnap(False)
|
||
|
with self.assertRaises(lzc_exc.NameTooLong):
|
||
|
lzc.lzc_get_holds(snap)
|
||
|
|
||
|
def test_get_holds_too_long_snap_name_2(self):
|
||
|
snap = ZFSTest.pool.getRoot().getTooLongSnap(True)
|
||
|
with self.assertRaises(lzc_exc.NameTooLong):
|
||
|
lzc.lzc_get_holds(snap)
|
||
|
|
||
|
def test_get_holds_invalid_snap_name(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap() + b'@bad'
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_get_holds(snap)
|
||
|
|
||
|
# A filesystem-like snapshot name is not recognized as
|
||
|
# an invalid name.
|
||
|
@unittest.expectedFailure
|
||
|
def test_get_holds_invalid_snap_name_2(self):
|
||
|
snap = ZFSTest.pool.getRoot().getFilesystem().getName()
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_get_holds(snap)
|
||
|
|
||
|
def test_release_hold(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
lzc.lzc_hold({snap: b'tag'})
|
||
|
ret = lzc.lzc_release({snap: [b'tag']})
|
||
|
self.assertEqual(len(ret), 0)
|
||
|
|
||
|
def test_release_hold_empty(self):
|
||
|
ret = lzc.lzc_release({})
|
||
|
self.assertEqual(len(ret), 0)
|
||
|
|
||
|
def test_release_hold_complex(self):
|
||
|
snap1 = ZFSTest.pool.getRoot().getSnap()
|
||
|
snap2 = ZFSTest.pool.getRoot().getSnap()
|
||
|
snap3 = ZFSTest.pool.getRoot().getFilesystem().getSnap()
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2, snap3])
|
||
|
|
||
|
lzc.lzc_hold({snap1: b'tag1'})
|
||
|
lzc.lzc_hold({snap1: b'tag2'})
|
||
|
lzc.lzc_hold({snap2: b'tag'})
|
||
|
lzc.lzc_hold({snap3: b'tag1'})
|
||
|
lzc.lzc_hold({snap3: b'tag2'})
|
||
|
|
||
|
holds = lzc.lzc_get_holds(snap1)
|
||
|
self.assertEqual(len(holds), 2)
|
||
|
holds = lzc.lzc_get_holds(snap2)
|
||
|
self.assertEqual(len(holds), 1)
|
||
|
holds = lzc.lzc_get_holds(snap3)
|
||
|
self.assertEqual(len(holds), 2)
|
||
|
|
||
|
release = {
|
||
|
snap1: [b'tag1', b'tag2'],
|
||
|
snap2: [b'tag'],
|
||
|
snap3: [b'tag2'],
|
||
|
}
|
||
|
ret = lzc.lzc_release(release)
|
||
|
self.assertEqual(len(ret), 0)
|
||
|
|
||
|
holds = lzc.lzc_get_holds(snap1)
|
||
|
self.assertEqual(len(holds), 0)
|
||
|
holds = lzc.lzc_get_holds(snap2)
|
||
|
self.assertEqual(len(holds), 0)
|
||
|
holds = lzc.lzc_get_holds(snap3)
|
||
|
self.assertEqual(len(holds), 1)
|
||
|
|
||
|
ret = lzc.lzc_release({snap3: [b'tag1']})
|
||
|
self.assertEqual(len(ret), 0)
|
||
|
holds = lzc.lzc_get_holds(snap3)
|
||
|
self.assertEqual(len(holds), 0)
|
||
|
|
||
|
def test_release_hold_before_auto_cleanup(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
ret = lzc.lzc_release({snap: [b'tag']})
|
||
|
self.assertEqual(len(ret), 0)
|
||
|
|
||
|
def test_release_hold_and_snap_destruction(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({snap: b'tag1'}, fd)
|
||
|
lzc.lzc_hold({snap: b'tag2'}, fd)
|
||
|
|
||
|
lzc.lzc_destroy_snaps([snap], defer=True)
|
||
|
self.assertExists(snap)
|
||
|
|
||
|
lzc.lzc_release({snap: [b'tag1']})
|
||
|
self.assertExists(snap)
|
||
|
|
||
|
lzc.lzc_release({snap: [b'tag2']})
|
||
|
self.assertNotExists(snap)
|
||
|
|
||
|
def test_release_hold_and_multiple_snap_destruction(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({snap: b'tag'}, fd)
|
||
|
|
||
|
lzc.lzc_destroy_snaps([snap], defer=True)
|
||
|
self.assertExists(snap)
|
||
|
|
||
|
lzc.lzc_destroy_snaps([snap], defer=True)
|
||
|
self.assertExists(snap)
|
||
|
|
||
|
lzc.lzc_release({snap: [b'tag']})
|
||
|
self.assertNotExists(snap)
|
||
|
|
||
|
def test_release_hold_missing_tag(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
ret = lzc.lzc_release({snap: [b'tag']})
|
||
|
self.assertEqual(len(ret), 1)
|
||
|
self.assertEqual(ret[0], snap + b'#tag')
|
||
|
|
||
|
def test_release_hold_missing_snap(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
|
||
|
ret = lzc.lzc_release({snap: [b'tag']})
|
||
|
self.assertEqual(len(ret), 1)
|
||
|
self.assertEqual(ret[0], snap)
|
||
|
|
||
|
def test_release_hold_missing_snap_2(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
|
||
|
ret = lzc.lzc_release({snap: [b'tag', b'another']})
|
||
|
self.assertEqual(len(ret), 1)
|
||
|
self.assertEqual(ret[0], snap)
|
||
|
|
||
|
def test_release_hold_across_pools(self):
|
||
|
snap1 = ZFSTest.pool.getRoot().getSnap()
|
||
|
snap2 = ZFSTest.misc_pool.getRoot().getSnap()
|
||
|
lzc.lzc_snapshot([snap1])
|
||
|
lzc.lzc_snapshot([snap2])
|
||
|
|
||
|
with cleanup_fd() as fd:
|
||
|
lzc.lzc_hold({snap1: b'tag'}, fd)
|
||
|
lzc.lzc_hold({snap2: b'tag'}, fd)
|
||
|
with self.assertRaises(lzc_exc.HoldReleaseFailure) as ctx:
|
||
|
lzc.lzc_release({snap1: [b'tag'], snap2: [b'tag']})
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.PoolsDiffer)
|
||
|
|
||
|
# Apparently the tag name is not verified,
|
||
|
# only its existence is checked.
|
||
|
@unittest.expectedFailure
|
||
|
def test_release_hold_too_long_tag(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap()
|
||
|
tag = b't' * 256
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
|
||
|
with self.assertRaises(lzc_exc.HoldReleaseFailure):
|
||
|
lzc.lzc_release({snap: [tag]})
|
||
|
|
||
|
# Apparently the full snapshot name is not checked for length
|
||
|
# and this snapshot is treated as simply missing.
|
||
|
@unittest.expectedFailure
|
||
|
def test_release_hold_too_long_snap_name(self):
|
||
|
snap = ZFSTest.pool.getRoot().getTooLongSnap(False)
|
||
|
|
||
|
with self.assertRaises(lzc_exc.HoldReleaseFailure):
|
||
|
lzc.lzc_release({snap: [b'tag']})
|
||
|
|
||
|
def test_release_hold_too_long_snap_name_2(self):
|
||
|
snap = ZFSTest.pool.getRoot().getTooLongSnap(True)
|
||
|
with self.assertRaises(lzc_exc.HoldReleaseFailure) as ctx:
|
||
|
lzc.lzc_release({snap: [b'tag']})
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameTooLong)
|
||
|
self.assertEqual(e.name, snap)
|
||
|
|
||
|
def test_release_hold_invalid_snap_name(self):
|
||
|
snap = ZFSTest.pool.getRoot().getSnap() + b'@bad'
|
||
|
with self.assertRaises(lzc_exc.HoldReleaseFailure) as ctx:
|
||
|
lzc.lzc_release({snap: [b'tag']})
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameInvalid)
|
||
|
self.assertEqual(e.name, snap)
|
||
|
|
||
|
def test_release_hold_invalid_snap_name_2(self):
|
||
|
snap = ZFSTest.pool.getRoot().getFilesystem().getName()
|
||
|
with self.assertRaises(lzc_exc.HoldReleaseFailure) as ctx:
|
||
|
lzc.lzc_release({snap: [b'tag']})
|
||
|
for e in ctx.exception.errors:
|
||
|
self.assertIsInstance(e, lzc_exc.NameInvalid)
|
||
|
self.assertEqual(e.name, snap)
|
||
|
|
||
|
def test_sync_missing_pool(self):
|
||
|
pool = b"nonexistent"
|
||
|
with self.assertRaises(lzc_exc.PoolNotFound):
|
||
|
lzc.lzc_sync(pool)
|
||
|
|
||
|
def test_sync_pool_forced(self):
|
||
|
pool = ZFSTest.pool.getRoot().getName()
|
||
|
lzc.lzc_sync(pool, True)
|
||
|
|
||
|
def test_reopen_missing_pool(self):
|
||
|
pool = b"nonexistent"
|
||
|
with self.assertRaises(lzc_exc.PoolNotFound):
|
||
|
lzc.lzc_reopen(pool)
|
||
|
|
||
|
def test_reopen_pool_no_restart(self):
|
||
|
pool = ZFSTest.pool.getRoot().getName()
|
||
|
lzc.lzc_reopen(pool, False)
|
||
|
|
||
|
def test_channel_program_missing_pool(self):
|
||
|
pool = b"nonexistent"
|
||
|
with self.assertRaises(lzc_exc.PoolNotFound):
|
||
|
lzc.lzc_channel_program(pool, b"return {}")
|
||
|
|
||
|
def test_channel_program_timeout(self):
|
||
|
pool = ZFSTest.pool.getRoot().getName()
|
||
|
zcp = b"""
|
||
|
for i = 1,10000 do
|
||
|
zfs.sync.snapshot('""" + pool + b"""@zcp' .. i)
|
||
|
end
|
||
|
"""
|
||
|
with self.assertRaises(lzc_exc.ZCPTimeout):
|
||
|
lzc.lzc_channel_program(pool, zcp, instrlimit=1)
|
||
|
|
||
|
def test_channel_program_memory_limit(self):
|
||
|
pool = ZFSTest.pool.getRoot().getName()
|
||
|
zcp = b"""
|
||
|
for i = 1,10000 do
|
||
|
zfs.sync.snapshot('""" + pool + b"""@zcp' .. i)
|
||
|
end
|
||
|
"""
|
||
|
with self.assertRaises(lzc_exc.ZCPSpaceError):
|
||
|
lzc.lzc_channel_program(pool, zcp, memlimit=1)
|
||
|
|
||
|
def test_channel_program_invalid_limits(self):
|
||
|
pool = ZFSTest.pool.getRoot().getName()
|
||
|
zcp = b"""
|
||
|
return {}
|
||
|
"""
|
||
|
with self.assertRaises(lzc_exc.ZCPLimitInvalid):
|
||
|
lzc.lzc_channel_program(pool, zcp, instrlimit=0)
|
||
|
with self.assertRaises(lzc_exc.ZCPLimitInvalid):
|
||
|
lzc.lzc_channel_program(pool, zcp, memlimit=0)
|
||
|
|
||
|
def test_channel_program_syntax_error(self):
|
||
|
pool = ZFSTest.pool.getRoot().getName()
|
||
|
zcp = b"""
|
||
|
inv+val:id
|
||
|
"""
|
||
|
with self.assertRaises(lzc_exc.ZCPSyntaxError) as ctx:
|
||
|
lzc.lzc_channel_program(pool, zcp)
|
||
|
self.assertTrue(b"syntax error" in ctx.exception.details)
|
||
|
|
||
|
def test_channel_program_sync_snapshot(self):
|
||
|
pool = ZFSTest.pool.getRoot().getName()
|
||
|
snapname = ZFSTest.pool.makeName(b"@zcp")
|
||
|
zcp = b"""
|
||
|
zfs.sync.snapshot('""" + snapname + b"""')
|
||
|
"""
|
||
|
lzc.lzc_channel_program(pool, zcp)
|
||
|
self.assertExists(snapname)
|
||
|
|
||
|
def test_channel_program_runtime_error(self):
|
||
|
pool = ZFSTest.pool.getRoot().getName()
|
||
|
|
||
|
# failing an assertion raises a runtime error
|
||
|
with self.assertRaises(lzc_exc.ZCPRuntimeError) as ctx:
|
||
|
lzc.lzc_channel_program(pool, b"assert(1 == 2)")
|
||
|
self.assertTrue(
|
||
|
b"assertion failed" in ctx.exception.details)
|
||
|
# invoking the error() function raises a runtime error
|
||
|
with self.assertRaises(lzc_exc.ZCPRuntimeError) as ctx:
|
||
|
lzc.lzc_channel_program(pool, b"error()")
|
||
|
|
||
|
def test_channel_program_nosync_runtime_error(self):
|
||
|
pool = ZFSTest.pool.getRoot().getName()
|
||
|
zcp = b"""
|
||
|
zfs.sync.snapshot('""" + pool + b"""@zcp')
|
||
|
"""
|
||
|
# lzc_channel_program_nosync() allows only "read-only" operations
|
||
|
with self.assertRaises(lzc_exc.ZCPRuntimeError) as ctx:
|
||
|
lzc.lzc_channel_program_nosync(pool, zcp)
|
||
|
self.assertTrue(
|
||
|
b"running functions from the zfs.sync" in ctx.exception.details)
|
||
|
|
||
|
def test_change_key_new(self):
|
||
|
with encrypted_filesystem() as (fs, _):
|
||
|
lzc.lzc_change_key(
|
||
|
fs, 'new_key',
|
||
|
props={b"keyformat": lzc.zfs_keyformat.ZFS_KEYFORMAT_RAW},
|
||
|
key=os.urandom(lzc.WRAPPING_KEY_LEN))
|
||
|
|
||
|
def test_change_key_missing_fs(self):
|
||
|
name = b"nonexistent"
|
||
|
|
||
|
with self.assertRaises(lzc_exc.FilesystemNotFound):
|
||
|
lzc.lzc_change_key(
|
||
|
name, 'new_key',
|
||
|
props={b"keyformat": lzc.zfs_keyformat.ZFS_KEYFORMAT_RAW},
|
||
|
key=os.urandom(lzc.WRAPPING_KEY_LEN))
|
||
|
|
||
|
def test_change_key_not_loaded(self):
|
||
|
with encrypted_filesystem() as (fs, _):
|
||
|
lzc.lzc_unload_key(fs)
|
||
|
with self.assertRaises(lzc_exc.EncryptionKeyNotLoaded):
|
||
|
lzc.lzc_change_key(
|
||
|
fs, 'new_key',
|
||
|
props={b"keyformat": lzc.zfs_keyformat.ZFS_KEYFORMAT_RAW},
|
||
|
key=os.urandom(lzc.WRAPPING_KEY_LEN))
|
||
|
|
||
|
def test_change_key_invalid_property(self):
|
||
|
with encrypted_filesystem() as (fs, _):
|
||
|
with self.assertRaises(lzc_exc.PropertyInvalid):
|
||
|
lzc.lzc_change_key(fs, 'new_key', props={b"invalid": b"prop"})
|
||
|
|
||
|
def test_change_key_invalid_crypt_command(self):
|
||
|
with encrypted_filesystem() as (fs, _):
|
||
|
with self.assertRaises(lzc_exc.UnknownCryptCommand):
|
||
|
lzc.lzc_change_key(fs, 'duplicate_key')
|
||
|
|
||
|
def test_load_key(self):
|
||
|
with encrypted_filesystem() as (fs, key):
|
||
|
lzc.lzc_unload_key(fs)
|
||
|
lzc.lzc_load_key(fs, False, key)
|
||
|
|
||
|
def test_load_key_invalid(self):
|
||
|
with encrypted_filesystem() as (fs, key):
|
||
|
lzc.lzc_unload_key(fs)
|
||
|
with self.assertRaises(lzc_exc.EncryptionKeyInvalid):
|
||
|
lzc.lzc_load_key(fs, False, os.urandom(lzc.WRAPPING_KEY_LEN))
|
||
|
|
||
|
def test_load_key_already_loaded(self):
|
||
|
with encrypted_filesystem() as (fs, key):
|
||
|
lzc.lzc_unload_key(fs)
|
||
|
lzc.lzc_load_key(fs, False, key)
|
||
|
with self.assertRaises(lzc_exc.EncryptionKeyAlreadyLoaded):
|
||
|
lzc.lzc_load_key(fs, False, key)
|
||
|
|
||
|
def test_load_key_missing_fs(self):
|
||
|
name = b"nonexistent"
|
||
|
|
||
|
with self.assertRaises(lzc_exc.FilesystemNotFound):
|
||
|
lzc.lzc_load_key(name, False, key=os.urandom(lzc.WRAPPING_KEY_LEN))
|
||
|
|
||
|
def test_unload_key(self):
|
||
|
with encrypted_filesystem() as (fs, _):
|
||
|
lzc.lzc_unload_key(fs)
|
||
|
|
||
|
def test_unload_key_missing_fs(self):
|
||
|
name = b"nonexistent"
|
||
|
|
||
|
with self.assertRaises(lzc_exc.FilesystemNotFound):
|
||
|
lzc.lzc_unload_key(name)
|
||
|
|
||
|
def test_unload_key_busy(self):
|
||
|
with encrypted_filesystem() as (fs, _):
|
||
|
with zfs_mount(fs):
|
||
|
with self.assertRaises(lzc_exc.DatasetBusy):
|
||
|
lzc.lzc_unload_key(fs)
|
||
|
|
||
|
def test_unload_key_not_loaded(self):
|
||
|
with encrypted_filesystem() as (fs, _):
|
||
|
lzc.lzc_unload_key(fs)
|
||
|
with self.assertRaises(lzc_exc.EncryptionKeyNotLoaded):
|
||
|
lzc.lzc_unload_key(fs)
|
||
|
|
||
|
def test_remap_missing_fs(self):
|
||
|
name = b"nonexistent"
|
||
|
|
||
|
with self.assertRaises(lzc_exc.DatasetNotFound):
|
||
|
lzc.lzc_remap(name)
|
||
|
|
||
|
def test_remap_invalid_fs(self):
|
||
|
ds = ZFSTest.pool.makeName(b"fs1")
|
||
|
snap = ds + b"@snap1"
|
||
|
|
||
|
lzc.lzc_snapshot([snap])
|
||
|
with self.assertRaises(lzc_exc.NameInvalid):
|
||
|
lzc.lzc_remap(snap)
|
||
|
|
||
|
def test_remap_too_long_fs_name(self):
|
||
|
name = ZFSTest.pool.makeTooLongName()
|
||
|
|
||
|
with self.assertRaises(lzc_exc.NameTooLong):
|
||
|
lzc.lzc_remap(name)
|
||
|
|
||
|
def test_remap(self):
|
||
|
name = ZFSTest.pool.makeName(b"fs1")
|
||
|
|
||
|
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 = b"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 = b"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(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")
|
||
|
tgt = ZFSTest.pool.makeName(b"target")
|
||
|
|
||
|
lzc.lzc_create(src)
|
||
|
lzc.lzc_rename(src, tgt)
|
||
|
self.assertNotExists(src)
|
||
|
self.assertExists(tgt)
|
||
|
|
||
|
@needs_support(lzc.lzc_rename)
|
||
|
def test_rename_nonexistent(self):
|
||
|
src = ZFSTest.pool.makeName(b"source")
|
||
|
tgt = ZFSTest.pool.makeName(b"target")
|
||
|
|
||
|
with self.assertRaises(lzc_exc.FilesystemNotFound):
|
||
|
lzc.lzc_rename(src, tgt)
|
||
|
|
||
|
@needs_support(lzc.lzc_rename)
|
||
|
def test_rename_existing_target(self):
|
||
|
src = ZFSTest.pool.makeName(b"source")
|
||
|
tgt = ZFSTest.pool.makeName(b"target")
|
||
|
|
||
|
lzc.lzc_create(src)
|
||
|
lzc.lzc_create(tgt)
|
||
|
with self.assertRaises(lzc_exc.FilesystemExists):
|
||
|
lzc.lzc_rename(src, tgt)
|
||
|
|
||
|
@needs_support(lzc.lzc_rename)
|
||
|
def test_rename_nonexistent_target_parent(self):
|
||
|
src = ZFSTest.pool.makeName(b"source")
|
||
|
tgt = ZFSTest.pool.makeName(b"parent/target")
|
||
|
|
||
|
lzc.lzc_create(src)
|
||
|
with self.assertRaises(lzc_exc.FilesystemNotFound):
|
||
|
lzc.lzc_rename(src, tgt)
|
||
|
|
||
|
@needs_support(lzc.lzc_rename)
|
||
|
def test_rename_parent_is_zvol(self):
|
||
|
src = ZFSTest.pool.makeName(b"source")
|
||
|
zvol = ZFSTest.pool.makeName(b"parent")
|
||
|
tgt = zvol + b"/target"
|
||
|
props = {b"volsize": 1024 * 1024}
|
||
|
|
||
|
lzc.lzc_create(src)
|
||
|
lzc.lzc_create(zvol, ds_type='zvol', props=props)
|
||
|
with self.assertRaises(lzc_exc.WrongParent):
|
||
|
lzc.lzc_rename(src, tgt)
|
||
|
|
||
|
@needs_support(lzc.lzc_destroy)
|
||
|
def test_destroy(self):
|
||
|
fs = ZFSTest.pool.makeName(b"test-fs")
|
||
|
|
||
|
lzc.lzc_create(fs)
|
||
|
lzc.lzc_destroy(fs)
|
||
|
self.assertNotExists(fs)
|
||
|
|
||
|
@needs_support(lzc.lzc_destroy)
|
||
|
def test_destroy_nonexistent(self):
|
||
|
fs = ZFSTest.pool.makeName(b"test-fs")
|
||
|
|
||
|
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']
|
||
|
BOOKMARKS = [b'bmark', b'bmark1', b'bmark2']
|
||
|
|
||
|
_cachefile_suffix = ".cachefile"
|
||
|
|
||
|
# XXX Whether to do a sloppy but much faster cleanup
|
||
|
# or a proper but slower one.
|
||
|
_recreate_pools = True
|
||
|
|
||
|
def __init__(self, size=128 * 1024 * 1024, readonly=False, filesystems=[]):
|
||
|
self._filesystems = filesystems
|
||
|
self._readonly = readonly
|
||
|
if sys.version_info < (3, 0):
|
||
|
self._pool_name = b'pool.' + bytes(uuid.uuid4())
|
||
|
else:
|
||
|
self._pool_name = b'pool.' + bytes(str(uuid.uuid4()),
|
||
|
encoding='utf-8')
|
||
|
self._root = _Filesystem(self._pool_name)
|
||
|
(fd, self._pool_file_path) = tempfile.mkstemp(
|
||
|
suffix='.zpool', prefix='tmp-')
|
||
|
if readonly:
|
||
|
cachefile = self._pool_file_path + _TempPool._cachefile_suffix
|
||
|
else:
|
||
|
cachefile = 'none'
|
||
|
self._zpool_create = [
|
||
|
'zpool', 'create', '-o', 'cachefile=' + cachefile,
|
||
|
'-O', 'mountpoint=legacy', self._pool_name, self._pool_file_path]
|
||
|
try:
|
||
|
os.ftruncate(fd, size)
|
||
|
os.close(fd)
|
||
|
|
||
|
subprocess.check_output(
|
||
|
self._zpool_create, stderr=subprocess.STDOUT)
|
||
|
|
||
|
for fs in filesystems:
|
||
|
lzc.lzc_create(self.makeName(fs))
|
||
|
|
||
|
self._bmarks_supported = self.isPoolFeatureEnabled('bookmarks')
|
||
|
|
||
|
if readonly:
|
||
|
# To make a pool read-only it must exported and re-imported
|
||
|
# with readonly option.
|
||
|
# The most deterministic way to re-import the pool is by using
|
||
|
# a cache file.
|
||
|
# But the cache file has to be stashed away before the pool is
|
||
|
# exported, because otherwise the pool is removed from the
|
||
|
# cache.
|
||
|
shutil.copyfile(cachefile, cachefile + '.tmp')
|
||
|
subprocess.check_output(
|
||
|
['zpool', 'export', '-f', self._pool_name],
|
||
|
stderr=subprocess.STDOUT)
|
||
|
os.rename(cachefile + '.tmp', cachefile)
|
||
|
subprocess.check_output(
|
||
|
['zpool', 'import', '-f', '-N', '-c', cachefile,
|
||
|
'-o', 'readonly=on', self._pool_name],
|
||
|
stderr=subprocess.STDOUT)
|
||
|
os.remove(cachefile)
|
||
|
|
||
|
except subprocess.CalledProcessError as e:
|
||
|
self.cleanUp()
|
||
|
if b'permission denied' in e.output:
|
||
|
raise unittest.SkipTest(
|
||
|
'insufficient privileges to run libzfs_core tests')
|
||
|
print('command failed: ', e.output)
|
||
|
raise
|
||
|
except Exception:
|
||
|
self.cleanUp()
|
||
|
raise
|
||
|
|
||
|
def reset(self):
|
||
|
if self._readonly:
|
||
|
return
|
||
|
|
||
|
if not self.__class__._recreate_pools:
|
||
|
snaps = []
|
||
|
for fs in [''] + self._filesystems:
|
||
|
for snap in self.__class__.SNAPSHOTS:
|
||
|
snaps.append(self.makeName(fs + '@' + snap))
|
||
|
self.getRoot().visitSnaps(lambda snap: snaps.append(snap))
|
||
|
lzc.lzc_destroy_snaps(snaps, defer=False)
|
||
|
|
||
|
if self._bmarks_supported:
|
||
|
bmarks = []
|
||
|
for fs in [''] + self._filesystems:
|
||
|
for bmark in self.__class__.BOOKMARKS:
|
||
|
bmarks.append(self.makeName(fs + '#' + bmark))
|
||
|
self.getRoot().visitBookmarks(
|
||
|
lambda bmark: bmarks.append(bmark))
|
||
|
lzc.lzc_destroy_bookmarks(bmarks)
|
||
|
self.getRoot().reset()
|
||
|
return
|
||
|
|
||
|
# On the Buildbot builders this may fail with "pool is busy"
|
||
|
# Retry 5 times before raising an error
|
||
|
retry = 0
|
||
|
while True:
|
||
|
try:
|
||
|
subprocess.check_output(
|
||
|
['zpool', 'destroy', '-f', self._pool_name],
|
||
|
stderr=subprocess.STDOUT)
|
||
|
subprocess.check_output(
|
||
|
self._zpool_create, stderr=subprocess.STDOUT)
|
||
|
break
|
||
|
except subprocess.CalledProcessError as e:
|
||
|
if b'pool is busy' in e.output and retry < 5:
|
||
|
retry += 1
|
||
|
time.sleep(1)
|
||
|
continue
|
||
|
else:
|
||
|
print('command failed: ', e.output)
|
||
|
raise
|
||
|
for fs in self._filesystems:
|
||
|
lzc.lzc_create(self.makeName(fs))
|
||
|
self.getRoot().reset()
|
||
|
|
||
|
def cleanUp(self):
|
||
|
try:
|
||
|
subprocess.check_output(
|
||
|
['zpool', 'destroy', '-f', self._pool_name],
|
||
|
stderr=subprocess.STDOUT)
|
||
|
except Exception:
|
||
|
pass
|
||
|
try:
|
||
|
os.remove(self._pool_file_path)
|
||
|
except Exception:
|
||
|
pass
|
||
|
try:
|
||
|
os.remove(self._pool_file_path + _TempPool._cachefile_suffix)
|
||
|
except Exception:
|
||
|
pass
|
||
|
try:
|
||
|
os.remove(
|
||
|
self._pool_file_path + _TempPool._cachefile_suffix + '.tmp')
|
||
|
except Exception:
|
||
|
pass
|
||
|
|
||
|
def makeName(self, relative=None):
|
||
|
if not relative:
|
||
|
return self._pool_name
|
||
|
if relative.startswith((b'@', b'#')):
|
||
|
return self._pool_name + relative
|
||
|
return self._pool_name + b'/' + relative
|
||
|
|
||
|
def makeTooLongName(self, prefix=None):
|
||
|
if not prefix:
|
||
|
prefix = b'x'
|
||
|
prefix = self.makeName(prefix)
|
||
|
pad_len = lzc.MAXNAMELEN + 1 - len(prefix)
|
||
|
if pad_len > 0:
|
||
|
return prefix + b'x' * pad_len
|
||
|
else:
|
||
|
return prefix
|
||
|
|
||
|
def makeTooLongComponent(self, prefix=None):
|
||
|
padding = b'x' * (lzc.MAXNAMELEN + 1)
|
||
|
if not prefix:
|
||
|
prefix = padding
|
||
|
else:
|
||
|
prefix = prefix + padding
|
||
|
return self.makeName(prefix)
|
||
|
|
||
|
def getRoot(self):
|
||
|
return self._root
|
||
|
|
||
|
def getFilesystem(self, fsname):
|
||
|
return _Filesystem(self._pool_name + b'/' + fsname)
|
||
|
|
||
|
def isPoolFeatureAvailable(self, feature):
|
||
|
output = subprocess.check_output(
|
||
|
['zpool', 'get', '-H', 'feature@' + feature, self._pool_name])
|
||
|
output = output.strip()
|
||
|
return output != ''
|
||
|
|
||
|
def isPoolFeatureEnabled(self, feature):
|
||
|
output = subprocess.check_output(
|
||
|
['zpool', 'get', '-H', 'feature@' + feature, self._pool_name])
|
||
|
output = output.split()[2]
|
||
|
return output in [b'active', b'enabled']
|
||
|
|
||
|
|
||
|
class _Filesystem(object):
|
||
|
|
||
|
def __init__(self, name):
|
||
|
self._name = name
|
||
|
self.reset()
|
||
|
|
||
|
def getName(self):
|
||
|
return self._name
|
||
|
|
||
|
def reset(self):
|
||
|
self._children = []
|
||
|
self._fs_id = 0
|
||
|
self._snap_id = 0
|
||
|
self._bmark_id = 0
|
||
|
|
||
|
def getFilesystem(self):
|
||
|
self._fs_id += 1
|
||
|
fsname = self._name + b'/fs' + str(self._fs_id).encode()
|
||
|
fs = _Filesystem(fsname)
|
||
|
self._children.append(fs)
|
||
|
return fs
|
||
|
|
||
|
def getProperty(self, propname, received=False):
|
||
|
if received:
|
||
|
output = subprocess.check_output(
|
||
|
['zfs', 'get', '-pH', '-o', 'received', propname, self._name])
|
||
|
else:
|
||
|
output = subprocess.check_output(
|
||
|
['zfs', 'get', '-pH', '-o', 'value', propname, self._name])
|
||
|
return output.strip()
|
||
|
|
||
|
def _makeSnapName(self, i):
|
||
|
return self._name + b'@snap' + str(i).encode()
|
||
|
|
||
|
def getSnap(self):
|
||
|
self._snap_id += 1
|
||
|
return self._makeSnapName(self._snap_id)
|
||
|
|
||
|
def _makeBookmarkName(self, i):
|
||
|
return self._name + b'#bmark' + bytes(i)
|
||
|
|
||
|
def getBookmark(self):
|
||
|
self._bmark_id += 1
|
||
|
return self._makeBookmarkName(self._bmark_id)
|
||
|
|
||
|
def _makeTooLongName(self, too_long_component):
|
||
|
if too_long_component:
|
||
|
return b'x' * (lzc.MAXNAMELEN + 1)
|
||
|
|
||
|
# Note that another character is used for one of '/', '@', '#'.
|
||
|
comp_len = lzc.MAXNAMELEN - len(self._name)
|
||
|
if comp_len > 0:
|
||
|
return b'x' * comp_len
|
||
|
else:
|
||
|
return b'x'
|
||
|
|
||
|
def getTooLongFilesystemName(self, too_long_component):
|
||
|
return self._name + b'/' + self._makeTooLongName(too_long_component)
|
||
|
|
||
|
def getTooLongSnap(self, too_long_component):
|
||
|
return self._name + b'@' + self._makeTooLongName(too_long_component)
|
||
|
|
||
|
def getTooLongBookmark(self, too_long_component):
|
||
|
return self._name + b'#' + self._makeTooLongName(too_long_component)
|
||
|
|
||
|
def _visitFilesystems(self, visitor):
|
||
|
for child in self._children:
|
||
|
child._visitFilesystems(visitor)
|
||
|
visitor(self)
|
||
|
|
||
|
def visitFilesystems(self, visitor):
|
||
|
def _fsVisitor(fs):
|
||
|
visitor(fs._name)
|
||
|
|
||
|
self._visitFilesystems(_fsVisitor)
|
||
|
|
||
|
def visitSnaps(self, visitor):
|
||
|
def _snapVisitor(fs):
|
||
|
for i in range(1, fs._snap_id + 1):
|
||
|
visitor(fs._makeSnapName(i))
|
||
|
|
||
|
self._visitFilesystems(_snapVisitor)
|
||
|
|
||
|
def visitBookmarks(self, visitor):
|
||
|
def _bmarkVisitor(fs):
|
||
|
for i in range(1, fs._bmark_id + 1):
|
||
|
visitor(fs._makeBookmarkName(i))
|
||
|
|
||
|
self._visitFilesystems(_bmarkVisitor)
|
||
|
|
||
|
|
||
|
# vim: softtabstop=4 tabstop=4 expandtab shiftwidth=4
|