You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
638 lines
29 KiB
Python
638 lines
29 KiB
Python
|
|
'''
|
|
ZFS frontend API
|
|
'''
|
|
|
|
import logging
|
|
import os
|
|
import stat
|
|
from typing import Dict, List, Optional, Union
|
|
|
|
from .exceptions import (
|
|
DatasetNotFound,
|
|
PermissionError,
|
|
PoolNotFound,
|
|
PropertyNotFound,
|
|
ValidationError,
|
|
)
|
|
from .types import Dataset, DatasetType, Property
|
|
from .validation import (
|
|
validate_dataset_path,
|
|
validate_metadata_property_name,
|
|
validate_native_property_name,
|
|
validate_pool_name,
|
|
validate_property_value,
|
|
)
|
|
|
|
log = logging.getLogger('simplezfs.zfs')
|
|
|
|
|
|
class ZFS:
|
|
'''
|
|
ZFS interface class. This API generally covers only the zfs(8) tool, for zpool(8) please see :class:`~ZPool`.
|
|
|
|
**ZFS implementation**
|
|
|
|
There are two ways how the API actually communicates with the ZFS filesystem:
|
|
|
|
* Using the CLI tools (:class:`~zfs.zfs_cli.ZFSCli`)
|
|
* Using the native API (:class:`~zfs.zfs_native.ZFSNative`)
|
|
|
|
You can select the API by either creating an instance of one of them or using :func:`~zfs.zfs.get_zfs`.
|
|
|
|
|
|
**Properties and Metadata**
|
|
|
|
The functions :func:`set_property`, :func:`get_property` and :func:`get_properties` wrap the ZFS get/set
|
|
functionality. To support so-called `user properties`, which are called `metadata` in this API, a default namespace
|
|
can be stored using `metadata_namespace` when instantiating the interface or by calling :func:`set_metadata_namespace`
|
|
at any time.
|
|
|
|
:note: Not setting a metadata namespace means that one can't set or get metadata properties, unless the overwrite
|
|
parameter for the get/set functions is used.
|
|
|
|
:param metadata_namespace: Default namespace
|
|
:param pe_helper: Privilege escalation (PE) helper to use for actions that require elevated privileges (root).
|
|
:param use_pe_helper: Whether to use the PE helper for creating and (u)mounting.#
|
|
:param kwargs: Extra arguments, ignored
|
|
'''
|
|
def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[str] = None,
|
|
use_pe_helper: bool = False, **kwargs) -> None:
|
|
self.metadata_namespace = metadata_namespace
|
|
self.pe_helper = pe_helper
|
|
self.use_pe_helper = use_pe_helper
|
|
|
|
@property
|
|
def metadata_namespace(self) -> Optional[str]:
|
|
'''
|
|
Returns the metadata namespace, which may be None if not set.
|
|
'''
|
|
return self._metadata_namespace
|
|
|
|
@metadata_namespace.setter
|
|
def metadata_namespace(self, namespace: str) -> None:
|
|
'''
|
|
Sets a new metadata namespace
|
|
|
|
:todo: validate!
|
|
'''
|
|
self._metadata_namespace = namespace
|
|
|
|
@property
|
|
def pe_helper(self) -> Optional[str]:
|
|
'''
|
|
Returns the pe_helper, which may be None if not set.
|
|
'''
|
|
return self._pe_helper
|
|
|
|
@pe_helper.setter
|
|
def pe_helper(self, helper: Optional[str]) -> None:
|
|
'''
|
|
Sets the privilege escalation (PE) helper. Some basic checks for existance and executablility are performed,
|
|
but these are not sufficient for secure operation and are provided to aid the user in configuring the library.
|
|
|
|
:note: This method does not follow symlinks.
|
|
|
|
:raises FileNotFoundError: if the script can't be found or is not executable.
|
|
'''
|
|
if helper is None:
|
|
log.debug('PE helper is None')
|
|
self._pe_helper = None
|
|
else:
|
|
candidate = helper.strip()
|
|
|
|
mode = os.lstat(candidate).st_mode
|
|
if not stat.S_ISREG(mode):
|
|
raise FileNotFoundError('PE helper must be a file')
|
|
if not os.access(candidate, os.X_OK):
|
|
raise FileNotFoundError('PE helper must be executable')
|
|
log.debug(f'Setting privilege escalation helper to "{candidate}"')
|
|
self._pe_helper = candidate
|
|
|
|
@property
|
|
def use_pe_helper(self) -> bool:
|
|
'''
|
|
Returns whether the privilege escalation (PE) helper should be used.
|
|
'''
|
|
return self._use_pe_helper
|
|
|
|
@use_pe_helper.setter
|
|
def use_pe_helper(self, use: bool) -> None:
|
|
'''
|
|
Enable or disable using the privilege escalation (PE) helper.
|
|
'''
|
|
self._use_pe_helper = use
|
|
|
|
def dataset_exists(self, name: str) -> bool:
|
|
'''
|
|
Checks is a dataset exists. This is done by querying for its `type` property.
|
|
|
|
:param name: Name of the dataset to check for.
|
|
:return: Whether the dataset exists.
|
|
'''
|
|
try:
|
|
return self.get_property(name, 'type') is not None
|
|
except (DatasetNotFound, PermissionError, PoolNotFound):
|
|
return False
|
|
except PropertyNotFound:
|
|
return True
|
|
return False
|
|
|
|
def get_dataset_info(self, name: str) -> Dataset:
|
|
'''
|
|
Returns basic information about a dataset. To retrieve its properties, see :func:`~ZFS.get_property` and
|
|
:func:`~ZFS.get_properties`.
|
|
|
|
:param name: The name of the dataset in question.
|
|
:returns: The dataset info.
|
|
:raises DatasetNotFound: If the dataset does not exist.
|
|
:raises ValidationError: If the name was invalid.
|
|
'''
|
|
raise NotImplementedError(f'{self} has not implemented this function')
|
|
|
|
def list_datasets(self, *, parent: Union[str, Dataset] = None) -> List[Dataset]:
|
|
'''
|
|
Lists all datasets known to the system. If ``parent`` is set to a pool or dataset name (or a :class:`~zfs.types.Dataset`),
|
|
lists all the children of that dataset.
|
|
|
|
:param parent: If set, list all child datasets.
|
|
:return: The list of datasets.
|
|
'''
|
|
raise NotImplementedError(f'{self} has not implemented this function')
|
|
|
|
def set_mountpoint(self, fileset: str, mountpoint: str, *, use_pe_helper: bool = False) -> None:
|
|
'''
|
|
Sets or changes the mountpoint property of a fileset. While this can be achieved using the generic function
|
|
:func:`~ZFS.set_property`, it allows for using the privilege escalation (PE) helper if so desired.
|
|
|
|
:param fileset: The fileset to modify.
|
|
:param mountpoint: The new value for the ``mountpoint`` property.
|
|
:param use_pe_helper: Overwrite the default for using the privilege escalation (PE) helper for this task.
|
|
:raises DatasetNotFound: if the fileset could not be found.
|
|
:raises ValidationError: if validating the parameters failed.
|
|
'''
|
|
# real_use_pe_helper = use_pe_helper if use_pe_helper is not None else self.use_pe_helper
|
|
raise NotImplementedError(f'not implemented yet')
|
|
|
|
def set_property(self, dataset: str, key: str, value: str, *, metadata: bool = False, overwrite_metadata_namespace: Optional[str] = None) -> None:
|
|
'''
|
|
Sets the ``value`` of the native property ``key``. By default, only native properties can be set. If
|
|
``metadata`` is set to **True**, the default metadata namespace is prepended or the one in
|
|
``overwrite_metadata_namespace`` is used if set to a valid string.
|
|
|
|
Example:
|
|
|
|
>>> z = ZFSCli(namespace='foo')
|
|
>>> z.set_property('tank/test', 'testprop', 'testval', metadata=True, overwrite_metadata_namespace='bar')
|
|
>>> z.get_property('tank/test', 'testprop')
|
|
Exception
|
|
>>> z.get_property('tank/test', 'testprop', metadata=True)
|
|
Exception
|
|
>>> z.get_property('tank/test', 'testprop', metadata=True, overwrite_metadata_namespace='bar')
|
|
Property(key='testprop', val='testval', source='local', namespace='bar')
|
|
|
|
:param dataset: Name of the dataset to set the property. Expects the full path beginning with the pool name.
|
|
:param key: Name of the property to set. For non-native properties, set ``metadata`` to **True** and overwrite
|
|
the default namespace using ``overwrite_metadata_namespace`` if required.
|
|
:param value: The new value to set.
|
|
:param metadata: If **True**, prepend the namespace to set a user (non-native) property.
|
|
:param overwrite_metadata_namespace: Overwrite the default metadata namespace for user (non-native) properties
|
|
:raises DatasetNotFound: If the dataset could not be found.
|
|
:raises ValidationError: If validating the parameters failed.
|
|
'''
|
|
if key.strip() == 'all' and not metadata:
|
|
raise ValidationError('"all" is not a valid property name')
|
|
if '/' not in dataset:
|
|
validate_pool_name(dataset)
|
|
else:
|
|
validate_dataset_path(dataset)
|
|
if metadata:
|
|
if overwrite_metadata_namespace:
|
|
prop_name = f'{overwrite_metadata_namespace}:{key}'
|
|
elif self.metadata_namespace:
|
|
prop_name = f'{self.metadata_namespace}:{key}'
|
|
else:
|
|
raise ValidationError('no metadata namespace set')
|
|
validate_metadata_property_name(prop_name)
|
|
else:
|
|
validate_native_property_name(key)
|
|
prop_name = key
|
|
validate_property_value(value)
|
|
self._set_property(dataset, prop_name, value, metadata)
|
|
|
|
def _set_property(self, dataset: str, key: str, value: str, is_metadata: bool) -> None:
|
|
'''
|
|
Actual implementation of the set_property function. This is to be implemented by the specific APIs. It is
|
|
called by set_property, which has done all the validation and thus the parameters can be trusted to be valid.
|
|
|
|
:param dataset: Name of the dataset to set the property. Expects the full path beginning with the pool name.
|
|
:param key: Name of the property to set. For non-native properties, set ``metadata`` to **True** and overwrite
|
|
the default namespace using ``overwrite_metadata_namespace`` if required.
|
|
:param value: The new value to set.
|
|
:param is_metadata: Indicates we're dealing with a metadata property.
|
|
:raises DatasetNotFound: If the dataset could not be found.
|
|
'''
|
|
raise NotImplementedError(f'{self} has not implemented this function')
|
|
|
|
def get_property(self, dataset: str, key: str, *, metadata: bool = False, overwrite_metadata_namespace: Optional[str] = None) -> Property:
|
|
'''
|
|
Gets a specific property named ``key`` from the ``dataset``. By default, only native properties are returned.
|
|
This behaviour can be changed by setting ``metadata`` to **True**, which uses the default `metadata_namespace` to
|
|
select the namespace. The namespace can be overwritten using ``overwrite_metadata_namespace`` for the duration of the
|
|
method invocaton.
|
|
|
|
:param dataset: Name of the dataset to get the property. Expects the full path beginning with the pool name.
|
|
:param key: Name of the property to set. For non-native properties, set ``metadata`` to **True** and overwrite
|
|
the default namespace using ``overwrite_metadata_namespace`` if required.
|
|
:param metadata: If **True**, prepend the namespace to set a user (non-native) property.
|
|
:param overwrite_metadata_namespace: Overwrite the default namespace for user (non-native) properties.
|
|
:raises DatasetNotFound: If the dataset does not exist.
|
|
:raises ValidationError: If validating the parameters failed.
|
|
'''
|
|
if key.strip() == 'all' and not metadata:
|
|
raise ValidationError('"all" is not a valid property, use get_properties instead')
|
|
if '/' not in dataset:
|
|
# got a pool here
|
|
validate_pool_name(dataset)
|
|
else:
|
|
validate_dataset_path(dataset)
|
|
if metadata:
|
|
if overwrite_metadata_namespace:
|
|
prop_name = f'{overwrite_metadata_namespace}:{key}'
|
|
elif self.metadata_namespace:
|
|
prop_name = f'{self.metadata_namespace}:{key}'
|
|
else:
|
|
raise ValidationError('no metadata namespace set')
|
|
validate_metadata_property_name(prop_name)
|
|
else:
|
|
validate_native_property_name(key)
|
|
prop_name = key
|
|
return self._get_property(dataset, prop_name, metadata)
|
|
|
|
def _get_property(self, dataset: str, key: str, is_metadata: bool) -> Property:
|
|
'''
|
|
Actual implementation of the get_property function. This is to be implemented by the specific APIs. It is
|
|
called by get_property, which has done all the validation and thus the parameters can be trusted to be valid.
|
|
|
|
:param dataset: Name of the dataset to get the property. Expects the full path beginning with the pool name.
|
|
:param key: Name of the property to set. For non-native properties, set ``metadata`` to **True** and overwrite
|
|
the default namespace using ``overwrite_metadata_namespace`` if required.
|
|
:param is_metadata: Indicates we're dealing with a metadata property.
|
|
:raises DatasetNotFound: If the dataset does not exist.
|
|
'''
|
|
raise NotImplementedError(f'{self} has not implemented this function')
|
|
|
|
def get_properties(self, dataset: str, *, include_metadata: bool = False) -> List[Property]:
|
|
'''
|
|
Gets all properties from the ``dataset``. By default, only native properties are returned. To include metadata
|
|
properties, set ``include_metadata`` to **True**. In this mode, all properties are included, regardless of
|
|
``metadata_namespace``, it is up to the user to filter the metadata.
|
|
|
|
:param dataset: Name of the dataset to get properties from. Expects the full path beginning with the pool name.
|
|
:param include_metadata: If **True**, returns metadata (user) properties in addition to native properties.
|
|
:raises DatasetNotFound: If the dataset does not exist.
|
|
:raises ValidationError: If validating the parameters failed.
|
|
'''
|
|
if '/' not in dataset:
|
|
validate_pool_name(dataset)
|
|
else:
|
|
validate_dataset_path(dataset)
|
|
return self._get_properties(dataset, include_metadata)
|
|
|
|
def _get_properties(self, dataset: str, include_metadata: bool):
|
|
'''
|
|
Actual implementation of the get_properties function. This is to be implemented by the specific APIs. It is
|
|
called by get_properties, which has done all the validation and thus the parameters can be trusted to be valid.
|
|
|
|
:param dataset: Name of the dataset to get properties from. Expects the full path beginning with the pool name.
|
|
:param include_metadata: If **True**, returns metadata (user) properties in addition to native properties.
|
|
:raises DatasetNotFound: If the dataset does not exist.
|
|
:return: A list of properties.
|
|
'''
|
|
raise NotImplementedError(f'{self} has not implemented this function')
|
|
|
|
def create_snapshot(
|
|
self,
|
|
dataset: str,
|
|
name: str,
|
|
*,
|
|
properties: Dict[str, str] = None,
|
|
metadata_properties: Dict[str, str] = None
|
|
) -> Dataset:
|
|
'''
|
|
Create a new snapshot from an existing dataset.
|
|
|
|
:param dataset: The dataset to snapshot.
|
|
:param name: Name of the snapshot (the part after the ``@``)
|
|
:param properties: Dict of native properties to set.
|
|
:param metadata_properties: Dict of native properties to set. For namespaces other than the default (or when
|
|
no default has been set, format the key using ``namespace:key``.
|
|
:return: Info about the newly created dataset.
|
|
:raises ValidationError: If validating the parameters failed.
|
|
:raises DatasetNotFOund: If the dataset can't be found.
|
|
'''
|
|
return self.create_dataset(
|
|
f'{dataset}@{name}',
|
|
dataset_type=DatasetType.SNAPSHOT,
|
|
properties=properties,
|
|
metadata_properties=metadata_properties
|
|
)
|
|
|
|
def create_bookmark(self, snapshot: str, name: str) -> Dataset:
|
|
'''
|
|
Create a new bookmark from an existing snapshot.
|
|
|
|
:param snapshot: The snapshot to attach a bookmark to.
|
|
:param name: Name of the bookmark (the part after the ``#``)
|
|
:return: Info about the newly created dataset.
|
|
:raises ValidationError: If validating the parameters failed.
|
|
:raises DatasetNotFound: If the snapshot can't be found.
|
|
'''
|
|
raise NotImplementedError(f'{self} has not implemented this function')
|
|
|
|
def create_fileset(
|
|
self,
|
|
name: str,
|
|
*,
|
|
mountpoint: str = None,
|
|
properties: Dict[str, str] = None,
|
|
metadata_properties: Dict[str, str] = None,
|
|
recursive: bool = True
|
|
) -> Dataset:
|
|
'''
|
|
Create a new fileset. For convenience, a ``mountpoint`` parameter can be given. If not **None**, it will
|
|
overwrite any `mountpoint` value in the ``properties`` dict.
|
|
|
|
:param name: Name of the new fileset (complete path in the ZFS hierarchy).
|
|
:ppram mountpoint: Convenience parameter for setting/overwriting the moutpoint property
|
|
:param properties: Dict of native properties to set.
|
|
:param metadata_properties: Dict of native properties to set. For namespaces other than the default (or when
|
|
no default has been set, format the key using ``namespace:key``.
|
|
:param recursive: Recursively create the parent fileset. Refer to the ZFS documentation about the `-p`
|
|
parameter for ``zfs create``.
|
|
:return: Info about the newly created dataset.
|
|
:raises ValidationError: If validating the parameters failed.
|
|
:raises DatasetNotFOund: If the parent dataset can't be found and ``recursive`` is `False`.
|
|
'''
|
|
if mountpoint is not None:
|
|
if properties is None:
|
|
properties = dict()
|
|
# TODO validate path
|
|
properties['mountpoint'] = mountpoint
|
|
|
|
return self.create_dataset(
|
|
name,
|
|
dataset_type=DatasetType.FILESET,
|
|
properties=properties,
|
|
metadata_properties=metadata_properties,
|
|
)
|
|
|
|
def create_volume(
|
|
self,
|
|
name: str,
|
|
*,
|
|
size: int,
|
|
sparse: bool = False,
|
|
blocksize: Optional[int] = None,
|
|
properties: Dict[str, str] = None,
|
|
metadata_properties: Dict[str, str] = None,
|
|
recursive: bool = False
|
|
) -> Dataset:
|
|
'''
|
|
Create a new volume of the given ``size`` (in bytes). If ``sparse`` is **True**, a sparse volume (also known
|
|
as thin provisioned) will be created. If ``blocksize`` is given, it overwrites the ``blocksize`` property.
|
|
|
|
:param name: Name of the new volume (complete path in the ZFS hierarchy).
|
|
:param size: The size (in `bytes`) for the new volume.
|
|
:param sparse: Whether to create a sparse volume. Requires ``size`` to be set.
|
|
:param blocksize: If set, overwrites the `blocksize` property. Provided for convenience.
|
|
:param properties: Dict of native properties to set.
|
|
:param metadata_properties: Dict of native properties to set. For namespaces other than the default (or when
|
|
no default has been set, format the key using ``namespace:key``.
|
|
:param recursive: Recursively create the parent fileset. Refer to the ZFS documentation about the `-p`
|
|
parameter for ``zfs create``.
|
|
:return: Info about the newly created dataset.
|
|
:raises ValidationError: If validating the parameters failed.
|
|
:raises DatasetNotFound: If the parent dataset can't be found and ``recursive`` is `False`.
|
|
'''
|
|
if blocksize is not None:
|
|
if properties is None:
|
|
properties = dict()
|
|
properties['blocksize'] = f'{blocksize}'
|
|
return self.create_dataset(
|
|
name,
|
|
dataset_type=DatasetType.VOLUME,
|
|
properties=properties,
|
|
metadata_properties=metadata_properties,
|
|
sparse=sparse,
|
|
size=size,
|
|
recursive=recursive
|
|
)
|
|
|
|
def create_dataset(
|
|
self,
|
|
name: str,
|
|
*,
|
|
dataset_type: DatasetType = DatasetType.FILESET,
|
|
properties: Dict[str, str] = None,
|
|
metadata_properties: Dict[str, str] = None,
|
|
sparse: bool = False,
|
|
size: Optional[int] = None,
|
|
recursive: bool = False
|
|
) -> Dataset:
|
|
'''
|
|
Create a new dataset. The ``dataset_type`` parameter contains the type of dataset to create. This is a generic
|
|
function to create datasets, you may want to take a look at the more specific functions (that essentially call
|
|
this one) for convenience:
|
|
|
|
* :func:`~ZFS.create_fileset`
|
|
* :func:`~ZFS.create_snapshot`
|
|
* :func:`~ZFS.create_volume`
|
|
|
|
Properties specified with this call will be included in the create operation and are thus atomic. An exception
|
|
applies for `filesets` with mountpoints that are neither ``none`` nor ``legacy`` on `Linux`.
|
|
|
|
.. note::
|
|
|
|
Bookmarks can't be created this way, use :func:`~ZFS.create_bookmark` for that.
|
|
|
|
.. warning::
|
|
|
|
On Linux, only root is allowed to manipulate the namespace (aka `mount`). Refer to :ref:`the_mount_problem` in
|
|
the documentation.
|
|
|
|
:param name: The name of the new dataset. This includes the full path, e.g. ``tank/data/newdataset``.
|
|
:param dataset_type: Indicates the type of the dataset to be created.
|
|
:param properties: A dict containing the properties for this new dataset. These are the native properties.
|
|
:param metadata_properties: The metadata properties to set. To use a different namespace than the default (or
|
|
when no default is set), use the ``namespace:key`` format for the dict keys.
|
|
:param sparse: For volumes, specifies whether a sparse (thin provisioned) or normal (thick provisioned) volume
|
|
should be created.
|
|
:param size: For volumes, specifies the size in bytes.
|
|
:param recursive: Recursively create the parent fileset. Refer to the ZFS documentation about the `-p`
|
|
parameter for ``zfs create``. This does not apply to types other than volumes or filesets.
|
|
:raises ValidationError: If validating the parameters failed.
|
|
:raises DatasetNotFound: If the dataset (snapshot) or parent dataset (filesets and volumes with `recursive`
|
|
set to `False`) can't be found.
|
|
'''
|
|
if dataset_type == DatasetType.BOOKMARK:
|
|
raise ValidationError('Bookmarks can\'t be created using this function.')
|
|
|
|
if '/' in name:
|
|
validate_dataset_path(name)
|
|
else:
|
|
validate_pool_name(name)
|
|
|
|
# we can't create a toplevel element
|
|
if '/' not in name and type in (DatasetType.FILESET, DatasetType.VOLUME):
|
|
raise ValidationError('Can\'t create a toplevel fileset or volume, use ZPool instead.')
|
|
|
|
# check the syntax of the properties
|
|
if properties is not None:
|
|
for k, v in properties.items():
|
|
validate_native_property_name(k)
|
|
validate_property_value(v)
|
|
|
|
_metadata_properties = dict() # type: Dict[str, str]
|
|
if metadata_properties is not None:
|
|
for k, v in metadata_properties:
|
|
# if the name has no namespace, add the default one if set
|
|
if ':' not in k:
|
|
if not self._metadata_namespace:
|
|
raise ValidationError(f'Metadata property {k} has no namespace and none is set globally')
|
|
else:
|
|
meta_name = f'{self._metadata_namespace}:{k}'
|
|
else:
|
|
meta_name = k
|
|
_metadata_properties[meta_name] = metadata_properties[k]
|
|
validate_metadata_property_name(meta_name)
|
|
|
|
if type(v) != str:
|
|
_metadata_properties[meta_name] = f'{v}'
|
|
validate_property_value(_metadata_properties[meta_name])
|
|
|
|
# sparse and size are reset for all but the VOLUME type
|
|
if dataset_type != DatasetType.VOLUME:
|
|
if sparse:
|
|
log.warning('Ignoring "sparse" it is only valid for volumes')
|
|
sparse = False
|
|
if size:
|
|
log.warning('Ignoring "size", it is only valid for volumes')
|
|
size = None
|
|
|
|
# validate type specifics
|
|
if dataset_type in (DatasetType.FILESET, DatasetType.VOLUME):
|
|
if '@' in name or '#' in name:
|
|
raise ValidationError('Volumes/Filesets can\'t contain @ (snapshot) or # (bookmark)')
|
|
|
|
# NOTE this assumes that we're not being called on the root dataset itself!
|
|
# check if the parent exists
|
|
parent_ds = '/'.join(name.split('/')[:-1])
|
|
if not self.dataset_exists(parent_ds) and not recursive:
|
|
raise DatasetNotFound(f'Parent dataset "{parent_ds}" does not exist and "recursive" is not set')
|
|
|
|
if dataset_type == DatasetType.VOLUME:
|
|
if not size:
|
|
raise ValidationError('Size must be specified for volumes')
|
|
try:
|
|
size = int(size)
|
|
except ValueError as e:
|
|
raise ValidationError('Size is not an integer') from e
|
|
|
|
if size < 1:
|
|
raise ValidationError('Size is too low')
|
|
|
|
if properties and 'blocksize' in properties:
|
|
try:
|
|
blocksize = int(properties['blocksize'])
|
|
except ValueError:
|
|
raise ValidationError('blocksize must be an integer')
|
|
if blocksize < 2 or blocksize > 128 * 1024: # zfs(8) version 0.8.1 lists 128KB as maximum
|
|
raise ValidationError('blocksize must be between 2 and 128kb (inclusive)')
|
|
if not ((blocksize & (blocksize - 1) == 0) and blocksize != 0):
|
|
raise ValidationError('blocksize must be a power of two')
|
|
|
|
elif dataset_type == DatasetType.SNAPSHOT:
|
|
if recursive:
|
|
log.warning('"recursive" set for snapshot or bookmark, ignored')
|
|
recursive = False
|
|
|
|
symbol = '@' if dataset_type == DatasetType.SNAPSHOT else '#'
|
|
|
|
if symbol not in name:
|
|
raise ValidationError(f'Name must include {symbol}name')
|
|
|
|
# check if parent exits
|
|
ds_name, ss_name = name.split(symbol)
|
|
if not self.dataset_exists(ds_name):
|
|
raise DatasetNotFound(f'The parent dataset "{ds_name}" could not be found')
|
|
|
|
# TODO
|
|
|
|
return self._create_dataset(name, dataset_type=dataset_type, properties=properties, metadata_properties=_metadata_properties, sparse=sparse, size=size, recursive=recursive)
|
|
|
|
def _create_dataset(
|
|
self,
|
|
name: str,
|
|
*,
|
|
dataset_type: DatasetType,
|
|
properties: Dict[str, str] = None,
|
|
metadata_properties: Dict[str, str] = None,
|
|
sparse: bool = False,
|
|
size: Optional[int] = None,
|
|
recursive: bool = False,
|
|
) -> Dataset:
|
|
raise NotImplementedError(f'{self} has not implemented this function')
|
|
|
|
def destroy_dataset(self, name: str, *, recursive: bool = False) -> None:
|
|
'''
|
|
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``
|
|
and ``bookmark``.
|
|
|
|
This function can't be used to destroy pools, please use :class:`~zfs.ZPool` instead.
|
|
|
|
Example:
|
|
|
|
>>> zfs = ZFSCli()
|
|
>>> zfs.destroy_dataset('pool/system/root@pre-distupgrade')
|
|
|
|
:note: This is a destructive process that can't be undone.
|
|
|
|
:param name: Name of the dataset to remove.
|
|
'''
|
|
raise NotImplementedError(f'{self} has not implemented this function')
|
|
|
|
|
|
def get_zfs(api: str = 'cli', metadata_namespace: Optional[str] = None, **kwargs) -> ZFS:
|
|
'''
|
|
Returns an instance of the desired ZFS API. Default is ``cli``.
|
|
|
|
Using this function is an alternative to instantiating one of the implementations yourself.
|
|
|
|
The parameters ``metadata_namespace`` and all of the ``kwargs`` are passed to the implementations constructor.
|
|
|
|
Example:
|
|
|
|
>>> from zfs import get_zfs, ZFS
|
|
>>> type(get_zfs('cli'))
|
|
<class 'zfs.zfs_cli.ZFSCli'>
|
|
>>> type(get_zfs('native'))
|
|
<class 'zfs.zfs_native.ZFSNative'>
|
|
>>> isinstance(get_zfs(), ZFS)
|
|
True
|
|
|
|
:param api: API to use, either ``cli`` for zfs(8) or ``native`` for the `libzfs_core` api.
|
|
:param metadata_namespace: Default namespace.
|
|
:param kwargs: Extra parameters to pass to the implementations constructor.
|
|
:return: An API instance.
|
|
:raises NotImplementedError: If an unknown API was requested.
|
|
'''
|
|
if api == 'cli':
|
|
from .zfs_cli import ZFSCli
|
|
return ZFSCli(metadata_namespace=metadata_namespace, **kwargs)
|
|
elif api == 'native':
|
|
from .zfs_native import ZFSNative
|
|
return ZFSNative(metadata_namespace=metadata_namespace, **kwargs)
|
|
raise NotImplementedError(f'The api "{api}" has not been implemented.')
|