Mount NFS destinations with nolock so they need no nfs-common (0.66.2)
An NFSv3 mount with locking makes the kernel register its lock manager with rpcbind on the Docker host itself. On a host without nfs-common the mount fails with a bare "connection refused" that reads like the NAS is unreachable, even though the NAS and its ports are fine. Only StackPilot ever writes the backup files on an NFS destination, one writer per file, so nothing needs locking. mount_options() now adds nolock unless the options already choose (lock, nolock, local_lock=) or ask for NFSv4, which locks in-protocol and needs no rpcbind. Existing destinations get their volume recreated once with the new options. The refused-mount hint now points at rpcbind/nfs-common on the Docker host first, and "protocol not supported" explains the missing kernel module (modprobe nfsv4). The form labels the option. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6c62803dc7
commit
34458c702c
6 files changed
+96
-10
No files matched your search
@@ -204,5 +204,60 @@ def test_refused_mount_says_what_to_do(monkeypatch):
|
||||
|
||||
with pytest.raises(svc.DestinationError) as info:
|
||||
svc._nfs_run("stackpilot-nfs-dest-1", ["mkdir", "-p", "/nfs"])
|
||||
assert "nfsvers=4" in str(info.value) and "port 2049" in str(info.value)
|
||||
assert "rpcbind" in str(info.value) and "2049" in str(info.value)
|
||||
assert client.all == [] # and nothing is left behind
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# nolock: NFS destinations that work without nfs-common on the Docker host
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"options, expected",
|
||||
[
|
||||
(None, "rw,nolock"),
|
||||
("", "rw,nolock"),
|
||||
("rw", "rw,nolock"),
|
||||
("rw,soft,", "rw,soft,nolock"),
|
||||
("rw,nfsvers=3", "rw,nfsvers=3,nolock"),
|
||||
("rw,lock", "rw,lock"), # asked for locking: respected
|
||||
("rw,nolock", "rw,nolock"),
|
||||
("rw,local_lock=all", "rw,local_lock=all"),
|
||||
("rw,nfsvers=4", "rw,nfsvers=4"), # v4 locks in-protocol, no rpcbind
|
||||
("rw,vers=4.1", "rw,vers=4.1"),
|
||||
],
|
||||
)
|
||||
def test_nolock_is_added_for_nfs_v3_only(options, expected):
|
||||
from services.backup_destination_service import mount_options
|
||||
|
||||
assert mount_options({"options": options}) == expected
|
||||
|
||||
|
||||
def test_existing_destination_is_remounted_with_nolock(monkeypatch):
|
||||
"""A destination saved with plain "rw" gets its volume recreated once."""
|
||||
from services import backup_destination_service as svc
|
||||
|
||||
client = FakeClient()
|
||||
monkeypatch.setattr("docker_client.get_client", lambda: client)
|
||||
dest, cfg = _nfs_dest("rw")
|
||||
name = f"{svc._NFS_VOLUME_PREFIX}{dest.id}"
|
||||
# As 0.66.1 and earlier created it.
|
||||
client.volumes.create(name, "local", {
|
||||
"type": "nfs", "o": "addr=10.10.1.80,rw", "device": ":/mnt/user/Share",
|
||||
}, {"stackpilot.nfs-destination": "1"})
|
||||
|
||||
svc._nfs_volume(dest, cfg)
|
||||
|
||||
assert client.volumes.items[name].attrs["Options"]["o"] == "addr=10.10.1.80,rw,nolock"
|
||||
|
||||
|
||||
def test_refused_mount_points_at_rpcbind_on_the_docker_host(monkeypatch):
|
||||
from services import backup_destination_service as svc
|
||||
|
||||
client = FakeClient(start_error="failed to mount local volume: connection refused")
|
||||
monkeypatch.setattr("docker_client.get_long_client", lambda: client)
|
||||
monkeypatch.setattr("services.stack_assets_service.ensure_helper_image", lambda c: None)
|
||||
with pytest.raises(svc.DestinationError) as info:
|
||||
svc._nfs_run("stackpilot-nfs-dest-1", ["true"])
|
||||
assert "nfs-common" in str(info.value)
|
||||
Reference in new issue
Block a user