implement 'zfs destroy'

main
svalouch 4 years ago
parent 644bf8e6bc
commit 7d2e6e2a0d

@ -6,7 +6,7 @@ import stat
import subprocess import subprocess
from typing import List, Optional from typing import List, Optional
from .exceptions import PEHelperException, ExternalPEHelperException from .exceptions import PEHelperException, ExternalPEHelperException, ValidationError
from .validation import validate_dataset_path, validate_pool_name from .validation import validate_dataset_path, validate_pool_name
@ -39,6 +39,15 @@ class PEHelperBase:
''' '''
raise NotImplementedError(f'{self} has not implemented this function') raise NotImplementedError(f'{self} has not implemented this function')
def zfs_destroy_dataset(self, dataset: str, recursive: bool, force_umount: bool):
'''
Destroy the given ``dataset``.
:raises ValidationError: If parameters do not validate.
:raises PEHelperException: If errors are encountered when running the helper.
'''
raise NotImplementedError(f'{self} has not implemented this function')
class ExternalPEHelper(PEHelperBase): class ExternalPEHelper(PEHelperBase):
''' '''
@ -156,3 +165,17 @@ class SudoPEHelper(PEHelperBase):
# TODO validate mountpoint fs # TODO validate mountpoint fs
self._execute_cmd(['zfs', 'set', f'mountpoint={mountpoint}', fileset]) self._execute_cmd(['zfs', 'set', f'mountpoint={mountpoint}', fileset])
def zfs_destroy_dataset(self, dataset: str, recursive: bool, force_umount: bool) -> None:
if '/' not in dataset:
raise ValidationError('Can\'t remove the pool itself')
validate_dataset_path(dataset)
args = ['zfs', 'destroy', '-p']
if recursive:
args.append('-r')
if force_umount:
args.append('-f')
args.append(dataset)
self._execute_cmd(args)

@ -651,7 +651,7 @@ class ZFS:
''' '''
raise NotImplementedError(f'{self} has not implemented this function') raise NotImplementedError(f'{self} has not implemented this function')
def destroy_dataset(self, name: str, *, recursive: bool = False) -> None: def destroy_dataset(self, dataset: str, *, recursive: bool = False, force_umount: bool = False) -> None:
''' '''
Destroy a dataset. This function tries to remove a dataset, optionally removing all children recursively if Destroy a dataset. This function tries to remove a dataset, optionally removing all children recursively if
``recursive`` is **True**. This function works on all types of datasets, ``fileset``, ``volume``, ``snapshot`` ``recursive`` is **True**. This function works on all types of datasets, ``fileset``, ``volume``, ``snapshot``
@ -666,7 +666,30 @@ class ZFS:
:note: This is a destructive process that can't be undone. :note: This is a destructive process that can't be undone.
:param name: Name of the dataset to remove. :param dataset: Name of the dataset to remove.
:param recursive: Whether to recursively delete child datasets such as snapshots.
:param force_umount: Forces umounting before destroying. Refer to ``ZFS(8)`` `zfs destroy` parameter ``-f``.
:raises ValidationError: If validating the parameters failed.
:raises DatasetNotFound: If the dataset can't be found.
'''
if '/' not in dataset:
raise ValidationError('Cannot destroy the pool using this function')
validate_dataset_path(dataset)
if not self.dataset_exists(dataset):
raise DatasetNotFound('The dataset could not be found')
self._destroy_dataset(dataset, recursive=recursive, force_umount=force_umount)
def _destroy_dataset(self, dataset: str, *, recursive: bool = False, force_umount: bool = False) -> None:
'''
Internal implementation of :func:`destroy_dataset`.
:param dataset: The name of the dataset to remove.
:param recursive: Whether to recursively delete child datasets such as snapshots.
:param force_umount: Forces umounting before destroying.
:raises ValidationError: If validating the parameters failed.
:raises DatasetNotFound: If the dataset can't be found.
''' '''
raise NotImplementedError(f'{self} has not implemented this function') raise NotImplementedError(f'{self} has not implemented this function')

@ -323,3 +323,50 @@ class ZFSCli(ZFS):
def create_bookmark(self, snapshot: str, name: str) -> Dataset: def create_bookmark(self, snapshot: str, name: str) -> Dataset:
validate_dataset_path(snapshot) validate_dataset_path(snapshot)
raise NotImplementedError() raise NotImplementedError()
def _destroy_dataset(self, dataset: str, *, recursive: bool = False, force_umount: bool = False) -> None:
args = [self.__exe, 'destroy', '-p']
if recursive:
args.append('-r')
if force_umount:
args.append('-f')
args.append(dataset)
log.debug(f'executing: {args}')
proc = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8')
if proc.returncode != 0 or len(proc.stderr) > 0:
log.debug(f'destroy_dataset: command failed, code={proc.returncode}, stderr="{proc.stderr}"')
if 'has children' in proc.stderr:
if recursive:
log.error(f'Dataset {dataset} has children and recursive was given, please report this')
else:
log.warning(f'Dataset {dataset} has children and thus cannot be destroyed without recursive=True')
raise Exception
# two possible messaes: (zfs destroy -p -r [-f] $fileset_with_snapshots)
# * 'cannot destroy snapshots: permission denied'
# * 'umount: only root can use "--types" option'
# The latter seems to originate from having `destroy` and `mount` via `zfs allow`.
elif ('cannot destroy' in proc.stderr and 'permission denied' in proc.stderr) or \
'only root can' in proc.stderr:
log.debug('Command output indicates that we need to run the PE Helper')
if self.use_pe_helper:
if self.pe_helper is not None:
log.info(f'Using pe_helper to remove {dataset}')
self.pe_helper.zfs_destroy_dataset(dataset, recursive, force_umount)
log.info(f'Dataset {dataset} destroyed (using pe_helper)')
else:
msg = 'Cannot destroy: No pe_helper set'
log.error(msg)
raise PermissionError(msg)
else:
log.error(f'Dataset "{dataset}" can\'t be destroyed due to lack of permissions. Please set a'
' PE helper')
raise PermissionError(proc.stderr)
else:
try:
self.handle_command_error(proc)
except PermissionError:
log.error('Permission denied, please use "zfs allow"')
raise
else:
log.info('Dataset destroyed successfully')

Loading…
Cancel
Save