Compare commits

...

4 Commits

Author SHA1 Message Date
Christian Schwarz 2c12675ee1 docs: 0.7 release notes
- compile release notes since 0.6.1
- move 'Next Release' from 'Changelog' to a new section
2026-02-15 14:09:05 +01:00
Christian Schwarz 76981d2037 docs: refactor: shorter expansion of :commit: 2026-02-15 14:09:05 +01:00
Christian Schwarz 075ecdac5b circleci: run platformtests against well-defined ZFS releases (#921)
Before this PR we platformtests in CI only against the ZFS version that
shipped with the CircleCI machine image (ZFS 2.2).

Changes in this PR:
- add an `command` for building OpenZFS `.deb`'s from a Git checkout,
and for caching those `.deb`s in CircleCI
- use that command in the platform test build matrix. ZFS versions:
**2.2.9**, **2.3.5**, and **2.4.0**

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 22:47:25 +01:00
Christian Schwarz 1dfae6829b zfs: fix platformtest compat with ZFS 2.3 & 2.4 (#922)
The error message when destroying a held snapshot no longer is `dataset
is busy` but `it's being held. Run 'zfs holds -r <snapshot>' to see
holders.`

The existing `destroySnapshotsErrorRegexp` parses both formats
correctly, but downstream test assertions in platformtest were checking
for the literal `dataset is busy` string.

refs
- the PR that adds ZFS 2.3 & 2.4 to CI:
https://github.com/zrepl/zrepl/pull/921

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 22:39:07 +01:00
9 changed files with 167 additions and 58 deletions
+76 -6
View File
@@ -51,6 +51,76 @@ commands:
paths:
- ~/.cache/uv
install-zfs-from-source:
parameters:
zfs_release:
type: string
steps:
- run:
name: Record kernel version for cache key
command: uname -r > /tmp/kernel-version
- restore_cache:
name: Restore ZFS native debs cache
keys:
- zfs-debs-v2-<<parameters.zfs_release>>-{{ checksum "/tmp/kernel-version" }}
- run:
# https://openzfs.github.io/openzfs-docs/Developer%20Resources/Building%20ZFS.html
name: Build ZFS <<parameters.zfs_release>> native debs (if not cached)
no_output_timeout: 20m
command: |
# CircleCI machine images have pyenv Python 3.13 shadowing the system
# Python 3.12. The apt python3-* packages (setuptools, cffi, etc.) only
# install for the system Python, and ZFS's dpkg-buildpackage needs them
# for --enable-pyzfs. Use the system Python so apt packages are visible.
export PYENV_VERSION=system
if [ -d /tmp/zfs-debs ]; then
echo "ZFS debs cache hit, skipping build"
exit 0
fi
sudo apt-get update
sudo apt-get install -y \
alien autoconf automake build-essential debhelper-compat dh-autoreconf \
dh-dkms dh-python dkms fakeroot gawk git libaio-dev libattr1-dev \
libblkid-dev libcurl4-openssl-dev libelf-dev libffi-dev libpam0g-dev \
libssl-dev libtirpc-dev libtool libudev-dev linux-headers-$(uname -r) lsb-release \
parallel po-debconf python3 python3-all-dev python3-cffi python3-dev \
python3-packaging python3-setuptools python3-sphinx uuid-dev zlib1g-dev
mkdir -p /tmp/zfs-build
cd /tmp/zfs-build
git clone --depth 1 --branch <<parameters.zfs_release>> https://github.com/openzfs/zfs.git zfs-src
cd zfs-src
sh autogen.sh
./configure
make native-deb
mkdir -p /tmp/zfs-debs
find /tmp/zfs-build -name '*.deb' -exec mv -t /tmp/zfs-debs/ {} +
- save_cache:
name: Save ZFS native debs cache
key: zfs-debs-v2-<<parameters.zfs_release>>-{{ checksum "/tmp/kernel-version" }}
paths:
- /tmp/zfs-debs
- run:
name: Install ZFS <<parameters.zfs_release>>
command: |
ls /tmp/zfs-debs/
# Only install the debs we need. Exclude dracut (conflicts with
# initramfs-tools), dkms (we have prebuilt modules), and packages
# we don't need (pyzfs, test, doc).
sudo apt-get install -y $(find /tmp/zfs-debs -name '*.deb' \
! -name '*dracut*' \
! -name '*dkms*' \
! -name '*initramfs*' \
! -name '*pyzfs*' \
! -name '*doc*' \
! -name '*test*' \
-print)
sudo modprobe zfs
- run:
name: Verify ZFS version
command: |
sudo zfs version
sudo zpool version
docs-publish-sh:
parameters:
push:
@@ -135,7 +205,7 @@ workflows:
goversion: [*latest-go-release]
goos: ["linux"]
goarch: ["amd64"]
image: ["ubuntu-2204:current", "ubuntu-2404:current"]
zfs_release: ["zfs-2.2.9", "zfs-2.3.5", "zfs-2.4.0"]
requires:
- test-go
- quickcheck-go-<< matrix.goarch >>-<< matrix.goos >>-<< matrix.goversion >>
@@ -239,10 +309,11 @@ jobs:
type: string
goarch:
type: string
image:
zfs_release:
type: string
machine:
image: <<parameters.image>>
# pinned (not :current) to keep ZFS build cache valid across runs
image: ubuntu-2404:2025.09.1
resource_class: medium
environment:
GOOS: <<parameters.goos>>
@@ -250,9 +321,8 @@ jobs:
steps:
- attach_workspace:
at: .
- run: sudo apt-get update
- run: sudo apt-get install -y zfsutils-linux
- run: sudo zfs version
- install-zfs-from-source:
zfs_release: <<parameters.zfs_release>>
- run: sudo make test-platform GOOS="$GOOS" GOARCH="$GOARCH"
test-go:
+2
View File
@@ -171,6 +171,8 @@ Run `make lint` and `make vet`.
Update the CI configuration `.circleci/config.yml`:
- Update Go version references (we reference the minimum and max supported version)
- Set `Makefile` `RELEASE_GOVERSION` to the new Go version
- Update the pinned ZFS release tags in the `platformtest` matrix (`zfs_release` parameter) to the latest patch releases of each OpenZFS branch (currently 2.2, 2.3, 2.4)
- Update the pinned Ubuntu machine image for `platformtest` (e.g. `ubuntu-2404:2025.09.1`) — this is pinned rather than `current` so the ZFS build cache stays valid across runs
Update docs build tooling:
- Update `uv` version in `.circleci/config.yml` (search for `astral.sh/uv/` and cache keys containing the version)
+30 -44
View File
@@ -13,43 +13,36 @@
Changelog
=========
The changelog summarizes bugfixes that are deemed relevant for users and package maintainers.
Developers should consult the git commit log or GitHub issue tracker.
0.7.0
-----
Next Release
------------
* |feature| Config file inclusion using ``include`` directive.
This allows distributing zrepl job definitions across multiple YAML files in a ``conf.d`` style directory.
(:commit:`4d6583e`, thanks, `@ZeyadTamimi <https://github.com/zeyadtamimi>`_).
* |feature| Add configurable timezone for snapshot name timestamps. The default remains UTC. (:commit:`b9b9ad1`, thanks, `@mjasnik <https://github.com/mjasnik>`_).
* |feature| Improve Grafana dashboard with additional panels and refinements (:commit:`3f75127`, thanks, `@deajan <https://github.com/deajan>`_).
* |bugfix| Fix replication of placeholder filesystems (:commit:`5615f49`).
* |bugfix| Avoid using the word "error" in info-level log lines about ZFS command execution (:commit:`d3b2295`, thanks, `@bakhtiyarneyman <https://github.com/bakhtiyarneyman>`_).
* |bugfix| Detect duplicate and internal job names at config parse time.
zrepl will now refuse to load a config with duplicate or internally reserved job names, instead of failing later at daemon startup.
(:commit:`860a9be`).
* |docs| Add installation instructions for openSUSE (:commit:`40c4827`, thanks, `@findesgh <https://github.com/findesgh>`_).
* |docs| Improve documentation on ``send_properties`` configuration and NFS/SMB considerations (:commit:`b5d8538`, thanks, `@Malvineous <https://github.com/Malvineous>`_).
* |docs| Warn more prominently about the risks of the ``not_replicated`` keep rule (:commit:`affe00a`, thanks, `@wxiaoguang <https://github.com/wxiaoguang>`_).
* |docs| Improve TLS/EasyRSA setup instructions (:commit:`e524b60`, thanks, `@alorimer <https://github.com/alorimer>`_).
* |docs| ``zrepl.github.io``: auto-publish from ``master`` branch, retire ``stable`` branch (:commit:`4f950bb`).
* |maint| ``zrepl status``: switch from the unmaintained ``cview`` fork back to the actively maintained ``tview`` library (:commit:`d7ede3f`).
* |maint| Update to Go 1.25 toolchain with Go 1.24 language level & Go dependencies
* |maint| Debian packages: proper bash completion placement (:commit:`2923009`)
* |maint| RPM packages: AlmaLinux 8 for RPM builds (:commit:`27f4ad1`).
* |maint| Improve build system & CI setup
The plan for the next release is to revisit how zrepl does snapshot management.
High-level goals:
- Make it easy to decouple snapshot management (snapshotting, pruning) from replication.
- Ability to include/exclude snapshots from replication.
This is useful for aforementioned decoupling, e.g., separate snapshot prefixes for local & remote replication.
Also, it makes explicit that by default, zrepl replicates all snapshots, and that
replication has no concept of "zrepl-created snapshots", which is a common misconception.
- Use of ``zfs snapshot`` comma syntax or channel programs to take snapshots of multiple
datasets atomically.
- Provide an alternative to the ``grid`` pruning policy.
Most likely something based on hourly/daily/weekly/monthly "trains" plus a count.
- Ability to prune at the granularity of the **group** of snapshots created at a given
time, as opposed to the individual snapshots within a dataset.
Maybe this will be addressed by the alternative to the ``grid`` pruning policy,
as it will likely be more predictable.
Those changes will likely come with some breakage in the config.
However, I want to avoid breaking **use cases** that are satisfied by the current design.
There will be beta/RC releases to give users a chance to evaluate.
0.7.0 (unreleased)
------------------
INCOMPLETE LIST OF CHANGES
* |maint| Update to Go 1.25 toolchain with Go 1.24 language level.
* |maint| Fix deprecations exposed by the toolchain update.
* |maint| Long-overdue update of all our dependencies & address deprecations.
* |maint| Publish docs from ``master`` branch, retire ``stable`` branch.
* |maint| The `make release-docker` in CircleCI produces executables that are bit-identical to my personal machine.
.. NOTE::
| zrepl is a spare-time project primarily developed by `Christian Schwarz <https://cschwarz.com>`_.
| You can support maintenance and feature development through one of the following services:
| |Donate via Patreon| |Donate via GitHub Sponsors| |Donate via Liberapay| |Donate via PayPal|
| Note that PayPal processing fees are relatively high for small donations.
| For SEPA wire transfer and **commercial support**, please `contact Christian directly <https://cschwarz.com>`_.
0.6.1
-----
@@ -65,13 +58,6 @@ INCOMPLETE LIST OF CHANGES
* |maint| run platform tests as part of CI.
* |maint| build: upgrade to Go 1.21 and update golangci-lint; minimum Go version for builds is now 1.20
.. NOTE::
| zrepl is a spare-time project primarily developed by `Christian Schwarz <https://cschwarz.com>`_.
| You can support maintenance and feature development through one of the following services:
| |Donate via Patreon| |Donate via GitHub Sponsors| |Donate via Liberapay| |Donate via PayPal|
| Note that PayPal processing fees are relatively high for small donations.
| For SEPA wire transfer and **commercial support**, please `contact Christian directly <https://cschwarz.com>`_.
0.6
---
@@ -258,7 +244,7 @@ Additional changelog:
* |bugfix| systemd + ``ssh+stdinserver``: automatically create ``/var/run/zrepl/stdinserver``
* |bugfix| crash if Prometheus listening socket cannot be opened
* [MAINTAINER NOTICE] ``Makefile`` refactoring, see :commit:`080f2c0`
* [MAINTAINER NOTICE] ``Makefile`` refactoring, see commit :commit:`080f2c0`
0.2
---
@@ -288,7 +274,7 @@ Additional changelog:
0.1.1
-----
* |bugfix| :issue:`162` :commit:`d6304f4` : fix I/O timeout errors on variable receive rate
* |bugfix| :issue:`162` commit :commit:`d6304f4` : fix I/O timeout errors on variable receive rate
* A significant reduction or sudden stall of the receive rate (e.g. recv pool has other I/O to do)
would cause a ``writev I/O timeout`` error after approximately ten seconds.
+1 -1
View File
@@ -182,6 +182,6 @@ extlinks = {
'issue':('https://github.com/zrepl/zrepl/issues/%s', 'issue #%s'),
'repomasterlink':('https://github.com/zrepl/zrepl/blob/master/%s', '%s'),
'sampleconf':('https://github.com/zrepl/zrepl/blob/master/internal/config/samples%s', 'internal/config/samples%s'),
'commit':('https://github.com/zrepl/zrepl/commit/%s', 'commit %s'),
'commit':('https://github.com/zrepl/zrepl/commit/%s', '%s'),
}
+33
View File
@@ -0,0 +1,33 @@
.. include:: global.rst.inc
.. _future:
Future
======
This page contains some notes about future plans for zrepl.
Then again, Christian only has very limited time available for zrepl maintenance these days.
So, don't count on any of this happening in the near future.
One big development that has happened in recent years is this fork of zrepl: https://github.com/dsh2dsh/zrepl
We should figure out whether we can pull in features from there.
The next major step in terms of new feature development for zrepl would be to revise snapshot management:
- Make it easy to decouple snapshot management (snapshotting, pruning) from replication.
- Ability to include/exclude snapshots from replication.
This is useful for aforementioned decoupling, e.g., separate snapshot prefixes for local & remote replication.
Also, it makes explicit that by default, zrepl replicates all snapshots, and that
replication has no concept of "zrepl-created snapshots", which is a common misconception.
- Use of ``zfs snapshot`` comma syntax or channel programs to take snapshots of multiple
datasets atomically.
- Provide an alternative to the ``grid`` pruning policy.
Most likely something based on hourly/daily/weekly/monthly "trains" plus a count.
- Ability to prune at the granularity of the **group** of snapshots created at a given
time, as opposed to the individual snapshots within a dataset.
Maybe this will be addressed by the alternative to the ``grid`` pruning policy,
as it will likely be more predictable.
Those changes will likely come with some breakage in the config.
However, I want to avoid breaking **use cases** that are satisfied by the current design.
There will be beta/RC releases to give users a chance to evaluate.
+1
View File
@@ -136,6 +136,7 @@ Table of Contents
usage
pr
changelog
future
GitHub Repository & Issue Tracker <https://github.com/zrepl/zrepl>
Chat: Matrix <https://matrix.to/#/#zrepl:matrix.org>
supporters
+9 -6
View File
@@ -573,8 +573,9 @@ func implReplicationIsResumableFullSend(ctx *platformtest.Context, setup replica
// due to step holds
if setup.expectDatasetIsBusyErrorWhenDestroySnapshotWhilePartiallyReplicated {
ctx.Logf("i=%v", i)
require.Error(ctx, err)
require.Contains(ctx, err.Error(), "dataset is busy")
dse, ok := err.(*zfs.DestroySnapshotsError)
require.True(ctx, ok, "expected *zfs.DestroySnapshotsError, got %T: %s", err, err)
require.True(ctx, dse.AllReasonIsHold(), "expected hold reason, got %v", dse.Reason)
}
}
@@ -756,11 +757,13 @@ func ReplicationIncrementalDestroysStepHoldsIffIncrementalStepHoldsAreDisabledBu
func ReplicationStepCompletedLostBehavior__GuaranteeResumability(ctx *platformtest.Context) {
scenario := replicationStepCompletedLostBehavior_impl(ctx, pdu.ReplicationGuaranteeKind_GuaranteeResumability)
require.Error(ctx, scenario.deleteSfs1Err, "protected by holds")
require.Contains(ctx, scenario.deleteSfs1Err.Error(), "dataset is busy")
dse1, ok := scenario.deleteSfs1Err.(*zfs.DestroySnapshotsError)
require.True(ctx, ok, "expected *zfs.DestroySnapshotsError, got %T: %s", scenario.deleteSfs1Err, scenario.deleteSfs1Err)
require.True(ctx, dse1.AllReasonIsHold(), "expected hold reason, got %v", dse1.Reason)
require.Error(ctx, scenario.deleteSfs2Err, "protected by holds")
require.Contains(ctx, scenario.deleteSfs2Err.Error(), "dataset is busy")
dse2, ok := scenario.deleteSfs2Err.(*zfs.DestroySnapshotsError)
require.True(ctx, ok, "expected *zfs.DestroySnapshotsError, got %T: %s", scenario.deleteSfs2Err, scenario.deleteSfs2Err)
require.True(ctx, dse2.AllReasonIsHold(), "expected hold reason, got %v", dse2.Reason)
require.Nil(ctx, scenario.finalReport.Error())
_ = fsversion(ctx, scenario.rfs, "@3") // @3 ade it to the other side
@@ -31,7 +31,7 @@ func UndestroyableSnapshotParsing(t *platformtest.Context) {
panic(dse.Filesystem)
}
require.Equal(t, []string{"4 5 6"}, dse.Undestroyable)
require.Equal(t, []string{"dataset is busy"}, dse.Reason)
require.True(t, dse.AllReasonIsHold(), "expected hold reason, got %v", dse.Reason)
}
}
+14
View File
@@ -1723,6 +1723,20 @@ type DestroySnapshotsError struct {
Reason []string
}
var destroySnapshotsReasonHeldRegexp = regexp.MustCompile(`^it's being held\. Run 'zfs holds -r .+' to see holders\.$`)
// AllReasonIsHold returns true if every undestroyable snapshot failed because
// it is held. ZFS < 2.4 reports "dataset is busy", ZFS >= 2.4 reports
// "it's being held. Run 'zfs holds -r <snapshot>' to see holders."
func (e *DestroySnapshotsError) AllReasonIsHold() bool {
for _, r := range e.Reason {
if r != "dataset is busy" && !destroySnapshotsReasonHeldRegexp.MatchString(r) {
return false
}
}
return true
}
func (e *DestroySnapshotsError) Error() string {
if len(e.Undestroyable) != len(e.Reason) {
panic(fmt.Sprintf("%v != %v", len(e.Undestroyable), len(e.Reason)))