fix(controller-zfs): unmount before destroy so deletes work on LXC hosts

DeleteVolume destroys with `zfs destroy -r -f -p`. The -f forces the
unmount, which issues umount2(MNT_FORCE). LXC's default seccomp profile
rejects that syscall (`reject_force_umount` in common.seccomp), so on a
ZFS server running inside an LXC container every delete of a mounted
dataset fails with:

  cannot unmount '/<parent>/pvc-...': unmount failed

and never recovers -- the PV stays Released and the dataset is stranded.
The retry in DeleteVolume does not help: its retryCondition only matches
"dataset is busy"/"target is busy", not this error.

A plain, non-forced unmount is permitted inside such containers, so
unmount explicitly before destroying. The unmount is best-effort: zvols
and already-unmounted datasets have nothing to unmount, and any real
problem still surfaces from the destroy that follows.

Refs #570

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsfbAFDXVKULBv26HxPEHF
This commit is contained in:
Addison Higham
2026-09-06 20:03:18 -06:00
co-authored by Claude Opus 5
parent 3974268272
commit 954d6e8223
2 changed files with 61 additions and 0 deletions
+14
View File
@@ -1471,6 +1471,20 @@ class ControllerZfsBaseDriver extends CsiBaseDriver {
// NOTE: -f does NOT allow deletes if dependent filesets exist
// NOTE: -R will recursively delete items + dependent filesets
// delete dataset
// Unmount explicitly before destroying. `zfs destroy -f` forces the
// unmount via umount2(MNT_FORCE), which LXC's default seccomp profile
// rejects (`reject_force_umount`), so on a containerised ZFS host every
// delete of a mounted dataset fails with "cannot unmount ... unmount
// failed" and never recovers. A plain unmount is permitted there.
//
// Best-effort: a dataset that is a zvol, already unmounted, or absent has
// nothing to unmount, and any real problem still surfaces from destroy.
try {
await zb.zfs.unmount(datasetName, { idempotent: true });
} catch (err) {
// fall through to destroy, which reports the actionable error
}
try {
await GeneralUtils.retry(
12,
+47
View File
@@ -1445,6 +1445,53 @@ class Zetabyte {
* @param {*} dataset
* @param {*} property
*/
/**
* zfs unmount [-f] filesystem|mountpoint
*
* Deliberately does NOT support -f. A forced unmount issues
* umount2(MNT_FORCE), which LXC's default seccomp profile rejects
* (`reject_force_umount`), so it fails with EPERM on containerised ZFS
* hosts. A plain unmount is permitted there and is sufficient.
*
* @param {*} dataset
* @param {*} options
*/
unmount: function (dataset, options = {}) {
if (!(arguments.length >= 1)) throw Error("Invalid arguments");
return new Promise((resolve, reject) => {
const idempotent =
"idempotent" in options
? options.idempotent
: "idempotent" in zb.options
? zb.options.idempotent
: false;
let args = [];
args.push("unmount");
args.push(dataset);
zb.exec(
zb.options.paths.zfs,
args,
{ timeout: zb.options.timeout },
function (error, stdout, stderr) {
if (
error &&
!(
idempotent &&
(stderr.includes("not currently mounted") ||
stderr.includes("not a mountpoint") ||
stderr.includes("dataset does not exist"))
)
)
return reject(zb.helpers.zfsError(error, stderr));
return resolve(stdout);
}
);
});
},
inherit: function (dataset, property) {
if (arguments.length != 2) throw Error("Invalid arguments");