mirror of
https://github.com/openzfs/zfs.git
synced 2025-10-01 11:26:49 +00:00

When attempting to debug performance problems on large systems, one of the major factors that affect performance is free space fragmentation. This heavily affects the allocation process, which is an area of active development in ZFS. Unfortunately, fragmenting a large pool for testing purposes is time consuming; it usually involves filling the pool and then repeatedly overwriting data until the free space becomes fragmented, which can take many hours. And even if the time is available, artificial workloads rarely generate the same fragmentation patterns as the natural workloads they're attempting to mimic. This patch has two parts. First, in zdb, we add the ability to export the full allocation map of the pool. It iterates over each vdev, printing every allocated segment in the ms_allocatable range tree. This can be done while the pool is online, though in that case the allocation map may actually be from several different TXGs as new ones are loaded on demand. The second is a new subcommand for zhack, zhack metaslab leak (and its supporting kernel changes). This is a zhack subcommand that imports a pool and then modified the range trees of the metaslabs, allowing the sync process to write them out normall. It does not currently store those allocations anywhere to make them reversible, and there is no corresponding free subcommand (which would be extremely dangerous); this is an irreversible process, only intended for performance testing. The only way to reclaim the space afterwards is to destroy the pool or roll back to a checkpoint. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Paul Dagnelie <paul.dagnelie@klarasystems.com> Sponsored-by: Klara, Inc. Sponsored-by: Wasabi Technology, Inc. Closes #17576
608 lines
14 KiB
Python
608 lines
14 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# 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.
|
|
#
|
|
|
|
"""
|
|
Exceptions that can be raised by libzfs_core operations.
|
|
"""
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
import errno
|
|
from ._constants import (
|
|
ECHRNG,
|
|
ECKSUM,
|
|
ETIME,
|
|
ZFS_ERR_CHECKPOINT_EXISTS,
|
|
ZFS_ERR_DISCARDING_CHECKPOINT,
|
|
ZFS_ERR_NO_CHECKPOINT,
|
|
ZFS_ERR_DEVRM_IN_PROGRESS,
|
|
ZFS_ERR_VDEV_TOO_BIG,
|
|
ZFS_ERR_WRONG_PARENT,
|
|
ZFS_ERR_RAIDZ_EXPAND_IN_PROGRESS,
|
|
zfs_errno
|
|
)
|
|
|
|
|
|
class ZFSError(Exception):
|
|
errno = None
|
|
message = None
|
|
name = None
|
|
|
|
def __str__(self):
|
|
if self.name is not None:
|
|
return "[Errno %d] %s: '%s'" % (
|
|
self.errno, self.message, self.name)
|
|
else:
|
|
return "[Errno %d] %s" % (self.errno, self.message)
|
|
|
|
def __repr__(self):
|
|
return "%s(%r, %r)" % (
|
|
self.__class__.__name__, self.errno, self.message)
|
|
|
|
|
|
class ZFSGenericError(ZFSError):
|
|
|
|
def __init__(self, errno, name, message):
|
|
self.errno = errno
|
|
self.message = message
|
|
self.name = name
|
|
|
|
|
|
class ZFSInitializationFailed(ZFSError):
|
|
message = "Failed to initialize libzfs_core"
|
|
|
|
def __init__(self, errno):
|
|
self.errno = errno
|
|
|
|
|
|
class MultipleOperationsFailure(ZFSError):
|
|
|
|
def __init__(self, errors, suppressed_count):
|
|
# Use first of the individual error codes
|
|
# as an overall error code. This is more consistent.
|
|
self.errno = errors[0].errno
|
|
self.errors = errors
|
|
# this many errors were encountered but not placed on the `errors` list
|
|
self.suppressed_count = suppressed_count
|
|
|
|
def __str__(self):
|
|
return "%s, %d errors included, %d suppressed" % (
|
|
ZFSError.__str__(self), len(self.errors), self.suppressed_count)
|
|
|
|
def __repr__(self):
|
|
return "%s(%r, %r, errors=%r, suppressed=%r)" % (
|
|
self.__class__.__name__, self.errno, self.message, self.errors,
|
|
self.suppressed_count)
|
|
|
|
|
|
class DatasetNotFound(ZFSError):
|
|
|
|
"""
|
|
This exception is raised when an operation failure can be caused by a
|
|
missing snapshot or a missing filesystem and it is impossible to
|
|
distinguish between the causes.
|
|
"""
|
|
errno = errno.ENOENT
|
|
message = "Dataset not found"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class DatasetExists(ZFSError):
|
|
|
|
"""
|
|
This exception is raised when an operation failure can be caused by an
|
|
existing snapshot or filesystem and it is impossible to distinguish between
|
|
the causes.
|
|
"""
|
|
errno = errno.EEXIST
|
|
message = "Dataset already exists"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class NotClone(ZFSError):
|
|
errno = errno.EINVAL
|
|
message = "Filesystem is not a clone, can not promote"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class FilesystemExists(DatasetExists):
|
|
message = "Filesystem already exists"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class FilesystemNotFound(DatasetNotFound):
|
|
message = "Filesystem not found"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class ParentNotFound(ZFSError):
|
|
errno = errno.ENOENT
|
|
message = "Parent not found"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class WrongParent(ZFSError):
|
|
errno = ZFS_ERR_WRONG_PARENT
|
|
message = "Parent dataset is not a filesystem"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class SnapshotExists(DatasetExists):
|
|
message = "Snapshot already exists"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class SnapshotNotFound(DatasetNotFound):
|
|
message = "Snapshot not found"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class SnapshotNotLatest(ZFSError):
|
|
errno = errno.EEXIST
|
|
message = "Snapshot is not the latest"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class SnapshotIsCloned(ZFSError):
|
|
errno = errno.EEXIST
|
|
message = "Snapshot is cloned"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class SnapshotIsHeld(ZFSError):
|
|
errno = errno.EBUSY
|
|
message = "Snapshot is held"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class DuplicateSnapshots(ZFSError):
|
|
errno = errno.EXDEV
|
|
message = "Requested multiple snapshots of the same filesystem"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class SnapshotFailure(MultipleOperationsFailure):
|
|
message = "Creation of snapshot(s) failed for one or more reasons"
|
|
|
|
def __init__(self, errors, suppressed_count):
|
|
super(SnapshotFailure, self).__init__(errors, suppressed_count)
|
|
|
|
|
|
class SnapshotDestructionFailure(MultipleOperationsFailure):
|
|
message = "Destruction of snapshot(s) failed for one or more reasons"
|
|
|
|
def __init__(self, errors, suppressed_count):
|
|
super(SnapshotDestructionFailure, self).__init__(
|
|
errors, suppressed_count)
|
|
|
|
|
|
class BookmarkExists(ZFSError):
|
|
errno = errno.EEXIST
|
|
message = "Bookmark already exists"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class BookmarkNotFound(ZFSError):
|
|
errno = errno.ENOENT
|
|
message = "Bookmark not found"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class BookmarkMismatch(ZFSError):
|
|
errno = errno.EINVAL
|
|
message = "source is not an ancestor of the new bookmark's dataset"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class BookmarkSourceInvalid(ZFSError):
|
|
errno = errno.EINVAL
|
|
message = "Bookmark source is not a valid snapshot or existing bookmark"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class BookmarkNotSupported(ZFSError):
|
|
errno = errno.ENOTSUP
|
|
message = "Bookmark feature is not supported"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class BookmarkFailure(MultipleOperationsFailure):
|
|
message = "Creation of bookmark(s) failed for one or more reasons"
|
|
|
|
def __init__(self, errors, suppressed_count):
|
|
super(BookmarkFailure, self).__init__(errors, suppressed_count)
|
|
|
|
|
|
class BookmarkDestructionFailure(MultipleOperationsFailure):
|
|
message = "Destruction of bookmark(s) failed for one or more reasons"
|
|
|
|
def __init__(self, errors, suppressed_count):
|
|
super(BookmarkDestructionFailure, self).__init__(
|
|
errors, suppressed_count)
|
|
|
|
|
|
class BadHoldCleanupFD(ZFSError):
|
|
errno = errno.EBADF
|
|
message = "Bad file descriptor as cleanup file descriptor"
|
|
|
|
|
|
class HoldExists(ZFSError):
|
|
errno = errno.EEXIST
|
|
message = "Hold with a given tag already exists on snapshot"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class HoldNotFound(ZFSError):
|
|
errno = errno.ENOENT
|
|
message = "Hold with a given tag does not exist on snapshot"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class HoldFailure(MultipleOperationsFailure):
|
|
message = "Placement of hold(s) failed for one or more reasons"
|
|
|
|
def __init__(self, errors, suppressed_count):
|
|
super(HoldFailure, self).__init__(errors, suppressed_count)
|
|
|
|
|
|
class HoldReleaseFailure(MultipleOperationsFailure):
|
|
message = "Release of hold(s) failed for one or more reasons"
|
|
|
|
def __init__(self, errors, suppressed_count):
|
|
super(HoldReleaseFailure, self).__init__(errors, suppressed_count)
|
|
|
|
|
|
class SnapshotMismatch(ZFSError):
|
|
errno = errno.ENODEV
|
|
message = "Snapshot is not descendant of source snapshot"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class StreamMismatch(ZFSError):
|
|
errno = errno.ENODEV
|
|
message = "Stream is not applicable to destination dataset"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class DestinationModified(ZFSError):
|
|
errno = errno.ETXTBSY
|
|
message = "Destination dataset has modifications that can not be undone"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class BadStream(ZFSError):
|
|
errno = ECKSUM
|
|
message = "Bad backup stream"
|
|
|
|
|
|
class StreamFeatureNotSupported(ZFSError):
|
|
errno = errno.ENOTSUP
|
|
message = "Stream contains unsupported feature"
|
|
|
|
|
|
class UnknownStreamFeature(ZFSError):
|
|
errno = errno.ENOTSUP
|
|
message = "Unknown feature requested for stream"
|
|
|
|
|
|
class StreamFeatureInvalid(ZFSError):
|
|
errno = errno.EINVAL
|
|
message = "Kernel modules must be upgraded to receive this stream"
|
|
|
|
|
|
class StreamFeatureIncompatible(ZFSError):
|
|
errno = errno.EINVAL
|
|
message = "Incompatible embedded feature with encrypted receive"
|
|
|
|
|
|
class StreamTruncated(ZFSError):
|
|
errno = zfs_errno.ZFS_ERR_STREAM_TRUNCATED
|
|
message = "incomplete stream"
|
|
|
|
|
|
class ReceivePropertyFailure(MultipleOperationsFailure):
|
|
message = "Receiving of properties failed for one or more reasons"
|
|
|
|
def __init__(self, errors, suppressed_count):
|
|
super(ReceivePropertyFailure, self).__init__(errors, suppressed_count)
|
|
|
|
|
|
class StreamIOError(ZFSError):
|
|
message = "I/O error while writing or reading stream"
|
|
|
|
def __init__(self, errno):
|
|
self.errno = errno
|
|
|
|
|
|
class ZIOError(ZFSError):
|
|
errno = errno.EIO
|
|
message = "I/O error"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class NoSpace(ZFSError):
|
|
errno = errno.ENOSPC
|
|
message = "No space left"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class QuotaExceeded(ZFSError):
|
|
errno = errno.EDQUOT
|
|
message = "Quota exceeded"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class DatasetBusy(ZFSError):
|
|
errno = errno.EBUSY
|
|
message = "Dataset is busy"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class NameTooLong(ZFSError):
|
|
errno = errno.ENAMETOOLONG
|
|
message = "Dataset name is too long"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class NameInvalid(ZFSError):
|
|
errno = errno.EINVAL
|
|
message = "Invalid name"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class SnapshotNameInvalid(NameInvalid):
|
|
message = "Invalid name for snapshot"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class FilesystemNameInvalid(NameInvalid):
|
|
message = "Invalid name for filesystem or volume"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class BookmarkNameInvalid(NameInvalid):
|
|
message = "Invalid name for bookmark"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class ReadOnlyPool(ZFSError):
|
|
errno = errno.EROFS
|
|
message = "Pool is read-only"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class SuspendedPool(ZFSError):
|
|
errno = errno.EAGAIN
|
|
message = "Pool is suspended"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class PoolNotFound(ZFSError):
|
|
errno = errno.EXDEV
|
|
message = "No such pool"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class PoolsDiffer(ZFSError):
|
|
errno = errno.EXDEV
|
|
message = "Source and target belong to different pools"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class FeatureNotSupported(ZFSError):
|
|
errno = errno.ENOTSUP
|
|
message = "Feature is not supported in this version"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class PropertyNotSupported(ZFSError):
|
|
errno = errno.ENOTSUP
|
|
message = "Property is not supported in this version"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class PropertyInvalid(ZFSError):
|
|
errno = errno.EINVAL
|
|
message = "Invalid property or property value"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class DatasetTypeInvalid(ZFSError):
|
|
errno = errno.EINVAL
|
|
message = "Specified dataset type is unknown"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class UnknownCryptCommand(ZFSError):
|
|
errno = errno.EINVAL
|
|
message = "Specified crypt command is invalid"
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class EncryptionKeyNotLoaded(ZFSError):
|
|
errno = errno.EACCES
|
|
message = "Encryption key is not currently loaded"
|
|
|
|
|
|
class EncryptionKeyAlreadyLoaded(ZFSError):
|
|
errno = errno.EEXIST
|
|
message = "Encryption key is already loaded"
|
|
|
|
|
|
class EncryptionKeyInvalid(ZFSError):
|
|
errno = errno.EACCES
|
|
message = "Incorrect encryption key provided"
|
|
|
|
|
|
class ZCPError(ZFSError):
|
|
errno = None
|
|
message = None
|
|
|
|
|
|
class ZCPSyntaxError(ZCPError):
|
|
errno = errno.EINVAL
|
|
message = "Channel program contains syntax errors"
|
|
|
|
def __init__(self, details):
|
|
self.details = details
|
|
|
|
|
|
class ZCPRuntimeError(ZCPError):
|
|
errno = ECHRNG
|
|
message = "Channel programs encountered a runtime error"
|
|
|
|
def __init__(self, details):
|
|
self.details = details
|
|
|
|
|
|
class ZCPLimitInvalid(ZCPError):
|
|
errno = errno.EINVAL
|
|
message = "Channel program called with invalid limits"
|
|
|
|
|
|
class ZCPTimeout(ZCPError):
|
|
errno = ETIME
|
|
message = "Channel program timed out"
|
|
|
|
|
|
class ZCPSpaceError(ZCPError):
|
|
errno = errno.ENOSPC
|
|
message = "Channel program exhausted the memory limit"
|
|
|
|
|
|
class ZCPMemoryError(ZCPError):
|
|
errno = errno.ENOMEM
|
|
message = "Channel program return value too large"
|
|
|
|
|
|
class ZCPPermissionError(ZCPError):
|
|
errno = errno.EPERM
|
|
message = "Channel programs must be run as root"
|
|
|
|
|
|
class CheckpointExists(ZFSError):
|
|
errno = ZFS_ERR_CHECKPOINT_EXISTS
|
|
message = "Pool already has a checkpoint"
|
|
|
|
|
|
class CheckpointNotFound(ZFSError):
|
|
errno = ZFS_ERR_NO_CHECKPOINT
|
|
message = "Pool does not have a checkpoint"
|
|
|
|
|
|
class CheckpointDiscarding(ZFSError):
|
|
errno = ZFS_ERR_DISCARDING_CHECKPOINT
|
|
message = "Pool checkpoint is being discarded"
|
|
|
|
|
|
class DeviceRemovalRunning(ZFSError):
|
|
errno = ZFS_ERR_DEVRM_IN_PROGRESS
|
|
message = "A vdev is currently being removed"
|
|
|
|
|
|
class DeviceTooBig(ZFSError):
|
|
errno = ZFS_ERR_VDEV_TOO_BIG
|
|
message = "One or more top-level vdevs exceed the maximum vdev size"
|
|
|
|
|
|
class RaidzExpansionRunning(ZFSError):
|
|
errno = ZFS_ERR_RAIDZ_EXPAND_IN_PROGRESS
|
|
message = "A raidz device is currently expanding"
|
|
|
|
# vim: softtabstop=4 tabstop=4 expandtab shiftwidth=4
|