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.

312 lines
12 KiB
ReStructuredText

#########################
Guide to python-simplezfs
#########################
5 years ago
Overview
********
The interfaces are similar to calling the zfs toolset on the command line. That is, there are not state holding classes
representing filesets or pools, each call must include the entire dataset path or pool name. There is also no way to
collect actions and run them at the end, each action is carried out immediately.
There are, however, two implementations of the functionality, using the ``cli`` tools and another one using the
`libzfs_core` ``native`` library. We'll focus on the ``cli`` version here.
Most of the functions raise a :class:`simplezfs.exceptions.ValidationError` with some helpful text if any of the data
turns out not to be valid. For example, including invalid or reserved strings in the dataset name raises this
exception.
5 years ago
Let's take a look at the two interfaces **ZFS** and **ZPool**...
.. warning:: All of the commands here attempt to modify something in the pool or dataset given as parameters. If run
with high enough permission (usually ``root``, but there's ``zfs allow`` that can delegate to lower-
privileged users, too) these commands can and will **delete** data! Always run these against pools, disks
and datasets that bear no important data! You have been warned.
The ZFS interface
*****************
The :class:`simplezfs.zfs.ZFS` is the interface that corresponds to the ``zfs(8)`` program. It holds very little state,
and it is recommended to get an instance through the function :func:`~simplezfs.zfs.get_zfs`. It selects the desired
5 years ago
implementation and passes the required parameters. At the very least, it requires the ``api``-parameter, which is a
string that selects the actual implementation, either ``cli`` or ``native``.
All our examples use the ``cli`` implementation for simplicity.
.. code-block:: pycon
>>> from zfs import get_zfs
>>> zfs = get_zfs('cli')
>>> zfs
<zfs.zfs_cli.ZFSCli object at 0x7f9f00faa9b0>
For the remainder of this guide, we're going to assume that the variable ``zfs`` always holds a
:class:`simplezfs.zfs_cli.ZFSCli` object.
5 years ago
Viewing data
============
To get an overview over the interface, we'll dive in and inspect our running system. Information is returned in the
form if :class:`simplezfs.types.Dataset` instances, which is a named tuple containing a set of fields. For simplicity,
5 years ago
we'll output only a few of its fields to not clobber the screen, so don't be alarmed if there seems to be information
missing: we just omitted the boring parts.
Listing datasets
----------------
By default when listing datasets, all of them are returned regardless of their type. That means that it includes
* volumes
* filesets
* snapshots
* bookmars
.. code-block:: pycon
>>> zfs.list_datasets()
<Dataset pool/system/root>
<Dataset pool/system/root@pre-distupgrade>
<Dataset tank/vol>
<Dataset tank/vol@test>
This is often unneccessary, and it allows to limit both by ``type`` and by including only datasets that are children
of another, and both at the same time:
.. code-block:: pycon
>>> zfs.list_datasets(type=DatasetType.SNAPSHOT)
<Dataset pool/root@pre-distupgrade>
<Dataset tank/vol@test>
>>> zfs.list_datasets(parent='pool/system')
<Dataset pool/root>
<Dataset pool/root@pre-distupgrade>
>>> zfs.list_datasets(parent='pool/system', type=DatasetType.SNAPSHOT)
<Dataset pool/root@pre-distupgrade>
Creating something new
======================
There are functions for creating the four different types of datasets with nice interfaces:
* :func:`~simplezfs.zfs.ZFS.create_fileset` for ordinary filesets, the most commonly used parameter is ``mountpoint``
for telling it where it should be mounted.
* :func:`~simplezfs.zfs.ZFS.create_volume` creates volumes, or ZVols, this features a parameter ``thin`` for creating
thin-provisioned or sparse volumes.
* :func:`~simplezfs.zfs.ZFS.create_snapshot` creates a snapshot on a volume or fileset.
* :func:`~simplezfs.zfs.ZFS.create_bookmark` creates a bookmark (on recent versions of ZFS).
5 years ago
These essentially call :func:`~simplezfs.zfs.ZFS.create_dataset`, which can be called directly, but its interface is
not as nice as the special purpose create functions.
5 years ago
Filesets
--------
Creating a fileset requires the dataset path, like this:
.. code-block:: pycon
>>> zfs.create_fileset('pool/test', mountpoint='/tmp/test')
<Dataset pool/test>
:todo: add create_dataset
Volumes
-------
Volumes are created similar to filesets, this example creates a thin-provisioned sparse volume:
.. code-block:: pycon
>>> zfs.create_volume('pool/vol', thin=True)
<Dataset pool/vol>
:todo: add create_dataset
Snapshots
---------
Snapshots are, like bookmarks, created on an existing fileset or volume, hence the first parameter to the function is
the dataset that is our base, and the second parameter is the name of the snapshot.
.. code-block:: pycon
>>> zfs.create_snapshot('pool/test', 'pre-distupgrade')
<Dataset pool/test@pre-distupgrade>
Bookmarks
---------
Like snapshots above, bookmarks are created on an existing fileset or volume.
.. code-block:: pycon
>>> zfs.create_bookmark('pool/test', 'book-20190723')
<Dataset pool/test#book-20190723>
Destroying things
=================
After creating some datasets of various kinds and playing around with some of their properties, it's time to clean up.
We'll use the ``destroy_*`` family of methods.
.. warning:: Bear in mind that things happening here are final and cannot be undone. When playing around, always make
sure not to run this on pools containing important data!
Filesets
--------
Volumes
-------
Snapshots
---------
Bookmarks
---------
Properties
==========
Properties are one of the many cool and useful features of ZFS. They control its behaviour (like ``compression``) or
return information about the internal states (like ``creation`` time).
.. note:: The python library does not validate the names of native properties, as these are subject to change with the
ZFS version and it would mean that the library needs an update every time a new ZFS version changes some of
3 years ago
these. Thus, it relies on validating the input for syntax based on the ZFS documentation of the OpenZFS
project and ZFS telling it that it did not like a name.
5 years ago
A word on metadata/user properties
----------------------------------
The API allows to get and set properties, for both ``native`` properties (the ones defined by ZFS, exposing information
or altering how it works) and ``user`` properties that we call **metadata properties** in the API.
When working with metadata properties, you need to supply a ``namespace`` to distinguish it from a native property.
This works by separating the namespace and the property name using a ``:`` character, so a property ``myprop``
in the namespace ``com.company.department`` becomes ``com.company.department:myprop`` in the ZFS property system. This
is done automatically for you if you supply a ``metadata_namespace`` when creating the ZFS instance and can be
overwritten when working with the get and set functions. It is also possible not to define the namespace and passing
it to the functions every time.
When you want to get or set a metadata property, set ``metadata`` to **True** when calling
:func:`~simplezfs.zfs.ZFS.get_property` or :func:`~simplezfs.zfs.ZFS.set_property`. This will cause it to automatically
prepend the namespace given on instantiation or to prepend the one given in the ``overwrite_metadata_namespace`` when
calling the functions. The name of the property **must not** include the namespace, though it may contain ``:``
characters on its own, properties of the form ``zfs:is:cool`` are valid afterall. ``:`` characters are never valid in
the context of native properties, and this is the reason why there is a separate switch to turn on metadata properties
when using these functions.
5 years ago
Error handling
--------------
If a property name is not valid or the value exceeds certain bounds, a :class:`simplezfs.exceptions.ValidationError` is
5 years ago
raised. This includes specifying a namespace in the property name if ``metadata`` is **False**, or exceeding the
length allowed for a metadata property (8192 - 1 bytes).
Though not an error for the ``zfs(8)`` utility, getting a non-existing metadata property also raises the above
exception to indicate that the property does not exist.
Getting a property
------------------
Getting properties is fairly straight-forward, especially for native properties:
.. code-block:: pycon
>>> zfs.get_property('tank/system/root', 'mountpoint')
Property(key='mountpoint', value='/', source='local', namespace=None)
For **metadata** properties, one needs to enable their usage by setting ``metadata`` to True. With a globally saved
namespace, it looks like this:
.. code-block:: pycon
>>> zfs = get_zfs('cli', metadata_namespace='com.company')
>>> zfs.get_property('tank/system/root', 'do_backup', metdata=True)
Property(key='do_backup', value='true', source='local', namespace='com.company')
If you don't specify a namespace when calling :func:`~simplezfs.zfs.get_zfs` or if you want to use a different
namespace for one call, specify the desired namespace in ``overwrite_metadata_namespace`` like so:
5 years ago
.. code-block:: pycon
>>> zfs.get_property('tank/system/root', 'requires', metadata=True, overwrite_metadata_namespace='user')
Property(key='requires', value='coffee', source='local', namespace='user')
This is the equivalent of calling ``zfs get user:requires tank/system/root`` on the shell.
Asking it to get a native property that does not exist results in an error:
.. code-block:: pycon
>>> zfs.get_property('tank/system/root', 'notexisting', metadata=False)
zfs.exceptions.PropertyNotFound: invalid property on dataset tank/test
Setting a property
------------------
The interface for setting both native and metadata properties works exactly like the get interface shown earlier,
though it obviously needs a value to set. We won't go into ZFS delegation system (``zfs allow``) and assume the
following is run using **root** privileges.
.. code-block:: pycon
>>> zfs.set_property('tank/service/backup', 'mountpoint', ''/backup')
Setting a metadata property works like this (again, like above):
.. code-block:: pycon
>>> zfs.set_property('tank/system/root', 'requires', 'tea', metadata=True, overwrite_metadata_namespace='user')
Listing properties
------------------
:todo: ``zfs.get_properties``
The ZPool interface
*******************
The :class:`simplezfs.zfs.ZPool` is the interface that corresponds to the ``zpool(8)`` program. It holds very little
state, and it is recommended to get an instance through the function :func:`~simplezfs.zpool.get_zpool`. It selects the
desired implementation and passes the required parameters. At the very least, it requires the ``api``-parameter, which
is a string that selects the actual implementation, either ``cli`` or ``native``.
5 years ago
All our examples use the ``cli`` implementation for simplicity.
.. code-block:: pycon
>>> from simplezfs import get_zpool
>>> zpool = get_zpool('cli')
>>> zpool
<zfs.zpool_cli.ZPoolCli object at 0x7f67d5254940>
For the remainder of this guide, we're going to assume that the variable ``zpool`` always holds a
:class:`simplezfs.zpool_cli.ZPoolCli` object.
5 years ago
Error handling
**************
We kept the most important part for last: handling errors. The module defines its own hierarchy with
:class:`simplezfs.exceptions.ZFSException` as toplevel exception. Various specific exceptions are based on ot. When
working with :class:`simplezfs.zfs.ZFS`, the three most common ones are:
* :class:`simplezfs.exceptions.ValidationError` which indicates that a name (e.g. dataset name) was invalid.
* :class:`simplezfs.exceptions.DatasetNotFound` is, like FileNotFound in standard python, indicating that the dataset
the module was instructed to work on (e.g. get/set properties, destroy) was not present.
* :class:`simplezfs.exceptions.PermissionError` is raised when the current users permissions are not sufficient to
perform the requested operation. While some actions can be delegated using ``zfs allow``, linux, for example, doesn't
allow non-root users to mount filesystems, which means that a non-root user may create filesets with a valid
mountpoint property, but it won't be mounted.
5 years ago
Examples
========
.. code-block:: pycon
>>> zfs.list_dataset(parent=':pool/name/invalid')
zfs.exceptions.ValidationError: malformed name
.. code-block:: pycon
>>> zfs.list_datasets(parent='pool/not/existing')
zfs.exceptions.DatasetNotFound: Dataset "pool/not/existing" not found