Files
computer-playbook/roles/svc-storage-nfs-server/tasks/03_controller_mount.yml
kevinveenbirkenbachandClaude Opus 5.5 9b7f25f2fe fix(svc-storage-nfs-client): stop requesting directory delegations from ganesha < 9.11
Every swarm job on ubuntu-26.04 runners (kernel 7.0) died at the NFS
probe write with `[Errno 121] Remote I/O error`. ganesha's NFSv4 debug log
in the rescue bundles shows the cause on every client, the same second as
the failure:

  Request 2: opcode 46 is OP_GET_DIR_DELEGATION
  Status of OP_GET_DIR_DELEGATION in position 2 = NFS4ERR_OP_ILLEGAL

Linux 6.19 added directory delegations: after ACCESS, CREATE, REMOVE or
RENAME the client adds GET_DIR_DELEGATION to the directory's next GETATTR
(`should_request_dir_deleg`, gated per request by the nfsv4 parameter
`directory_delegations`). nfs-ganesha before 9.11 routes op 46 to
`nfs4_op_illegal`, whose reply carries opcode 10044; the client decoder
expects 46 and fails the call with EREMOTEIO. Upstream fixed it in
nfs-ganesha#1385 (98eb77c4, first in V9.11), which answers NOTSUPP; the
client then drops the capability. The distro packages the role installs
predate it: Debian trixie 6.5-5, Ubuntu 26.04 6.5-8.

Every client and the controller now read the server's installed
nfs-ganesha release and, below 9.11, set `directory_delegations=N` right
after the mount and before the first access (the mount itself never sends
op 46), plus `options nfsv4 directory_delegations=N` in /etc/modprobe.d for
later module loads. No ganesha release grants directory delegations, so
the end state equals a fixed server. The parameter is kernel-wide and is
only ever set to N, so clients sharing one kernel cannot flip it back on
each other. Servers on 9.11 or later and the kernel nfsd flavor keep the
client default.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 16:28:52 +02:00

66 lines
2.2 KiB
YAML

---
- name: "🔍 Detect controller {{ DIR_VAR_LIB }} pre-mount state"
ansible.builtin.script:
cmd: "{{ lookup('path_absolute', 'roles/svc-storage-nfs-client/files/shell/premount_state.sh') }}"
executable: /bin/bash
environment:
DIR_VAR_LIB: "{{ DIR_VAR_LIB }}"
register: _ctrl_premount_state
changed_when: false
- name: "✅ Assert controller {{ DIR_VAR_LIB }} has no pre-existing data (mount hides it)"
ansible.builtin.assert:
that:
- >-
'STATE=mounted' in _ctrl_premount_state.stdout
or 'STATE=empty' in _ctrl_premount_state.stdout
fail_msg: >-
Pre-existing local data found at controller {{ DIR_VAR_LIB }}; NFS
mount-over would silently hide it. Migrate or remove the local tree
on the controller, then re-run the deploy. Probe said:
{{ _ctrl_premount_state.stdout | trim }}
- name: "✅ Ensure controller mount point exists"
ansible.builtin.file:
path: "{{ DIR_VAR_LIB }}"
state: directory
owner: root
group: root
mode: "0755"
- name: "📦 Install the NFS client"
package_install:
id: nfs-client
- name: "🔎 Read current mount source at controller {{ DIR_VAR_LIB }}"
ansible.builtin.command:
cmd: "findmnt -n -o SOURCE {{ DIR_VAR_LIB }}"
register: _ctrl_mount_src
changed_when: false
failed_when: false
- name: "🔌 Unmount foreign source so the ephemeral (re)mount is not refused"
ansible.posix.mount:
path: "{{ DIR_VAR_LIB }}"
state: unmounted
when:
- "'STATE=mounted' in _ctrl_premount_state.stdout"
- (_ctrl_mount_src.stdout | trim) | length > 0
- (_ctrl_mount_src.stdout | trim) != STORAGE_NFS_CONTROLLER_SRC
- name: "🔗 Mount cluster-shared infinito state on controller"
ansible.posix.mount:
path: "{{ DIR_VAR_LIB }}"
src: "{{ STORAGE_NFS_CONTROLLER_SRC }}"
fstype: "{{ STORAGE_NFS_FSTYPE }}"
opts: "{{ STORAGE_NFS_MOUNT_OPTS }}"
state: ephemeral
register: _ctrl_nfs_mount
retries: 12
delay: 5
until: _ctrl_nfs_mount is succeeded
- name: "🔧 Align the controller's directory delegations with the ganesha server"
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/svc-storage-nfs-client/tasks/directory_delegations.yml') }}"
when: lookup('nfs_flavor') == 'ganesha'