Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAS-133274 / 25.04 / move path_to_dataset outside process pool #15272

Merged
merged 5 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 6 additions & 19 deletions src/middlewared/middlewared/plugins/zfs_/dataset_actions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import errno
import libzfs
import os

from middlewared.schema import accepts, Bool, Dict, Str
from middlewared.service import CallError, Service
from middlewared.utils.mount import getmntinfo
from middlewared.utils.path import is_child
from middlewared.plugins.zfs_.utils import path_to_dataset_impl


def handle_ds_not_found(error_code: int, ds_name: str):
Expand All @@ -31,22 +29,11 @@ def path_to_dataset(self, path, mntinfo=None):
can be raised by a failed call to os.stat() are
possible.
"""
boot_pool = self.middleware.call_sync("boot.pool_name")

st = os.stat(path)
if mntinfo is None:
mntinfo = getmntinfo(st.st_dev)[st.st_dev]
else:
mntinfo = mntinfo[st.st_dev]

ds_name = mntinfo['mount_source']
if mntinfo['fs_type'] != 'zfs':
raise CallError(f'{path}: path is not a ZFS filesystem')

if is_child(ds_name, boot_pool):
raise CallError(f'{path}: path is on boot pool')

return ds_name
# NOTE: there is no real reason to call this method
# since it uses a child process in the process pool.
# It's more efficient to just import `path_to_dataset_impl`
# and call it directly.
return path_to_dataset_impl(path, mntinfo)

def child_dataset_names(self, path):
# return child datasets given a dataset `path`.
Expand Down
68 changes: 66 additions & 2 deletions src/middlewared/middlewared/plugins/zfs_/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
import os
import re

from middlewared.service_exception import MatchNotFound
from middlewared.service_exception import CallError, MatchNotFound
from middlewared.utils.filesystem.constants import ZFSCTL
from middlewared.utils.mount import getmntinfo
from middlewared.utils.path import is_child
from middlewared.plugins.audit.utils import (
AUDIT_DEFAULT_FILL_CRITICAL, AUDIT_DEFAULT_FILL_WARNING
)
from middlewared.plugins.boot import BOOT_POOL_NAME
from middlewared.utils.tdb import (
get_tdb_handle,
TDBBatchAction,
Expand All @@ -20,7 +23,13 @@

logger = logging.getLogger(__name__)

__all__ = ["zvol_name_to_path", "zvol_path_to_name", "get_snapshot_count_cached"]
__all__ = [
"get_snapshot_count_cached",
"path_to_dataset_impl",
"paths_to_datasets_impl",
"zvol_name_to_path",
"zvol_path_to_name",
]

LEGACY_USERPROP_PREFIX = 'org.freenas'
USERPROP_PREFIX = 'org.truenas'
Expand Down Expand Up @@ -299,3 +308,58 @@ def iter_datasets(out, datasets_in, batch_ops):
logger.warning('Failed to update cached snapshot counts', exc_info=True)

return out


def paths_to_datasets_impl(
paths: list[str],
mntinfo: dict | None = None
) -> dict | dict[str, str]:
"""
Convert `paths` to a dictionary of ZFS dataset names. This
performs lookup through mountinfo.

Anticipated error conditions are that paths are not
on ZFS or if the boot pool underlies the path. In
addition to this, all the normal exceptions that
can be raised by a failed call to os.stat() are
possible. If any exception occurs, the dataset name
will be set to None in the dictionary.
"""
rv = dict()
if mntinfo is None:
mntinfo = getmntinfo()

for path in paths:
try:
rv[path] = path_to_dataset_impl(path, mntinfo)
except Exception:
rv[path] = None

return rv


def path_to_dataset_impl(path: str, mntinfo: dict | None = None) -> str:
"""
Convert `path` to a ZFS dataset name. This
performs lookup through mountinfo.

Anticipated error conditions are that path is not
on ZFS or if the boot pool underlies the path. In
addition to this, all the normal exceptions that
can be raised by a failed call to os.stat() are
possible.
"""
st = os.stat(path)
if mntinfo is None:
mntinfo = getmntinfo(st.st_dev)[st.st_dev]
else:
mntinfo = mntinfo[st.st_dev]

ds_name = mntinfo['mount_source']
if mntinfo['fs_type'] != 'zfs':
raise CallError(f'{path}: path is not a ZFS filesystem')

if is_child(ds_name, BOOT_POOL_NAME):
raise CallError(f'{path}: path is on boot pool')

return ds_name
Loading