"""Defines a NFS-Ganesha container"""
daemon_type = 'nfs'
- entrypoint = '/usr/bin/ganesha.nfsd'
+ entrypoint = '/usr/local/scripts/ganesha-entrypoint.sh'
+ ganesha_binary = '/usr/bin/ganesha.nfsd'
+ entrypoint_script_name = 'ganesha-entrypoint.sh'
daemon_args = ['-F', '-L', 'STDERR']
required_files = ['ganesha.conf', 'idmap.conf']
mounts[os.path.join(data_dir, 'config')] = '/etc/ceph/ceph.conf:z'
mounts[os.path.join(data_dir, 'keyring')] = '/etc/ceph/keyring:z'
mounts[os.path.join(data_dir, 'etc/ganesha')] = '/etc/ganesha:z'
+ mounts[
+ os.path.join(data_dir, self.entrypoint_script_name)
+ ] = self.entrypoint
if self.rgw:
cluster = self.rgw.get('cluster', 'ceph')
rgw_user = self.rgw.get('user', 'admin')
ctx.container_engine.path,
'exec',
container_id,
- NFSGanesha.entrypoint,
+ NFSGanesha.ganesha_binary,
'-v',
],
verbosity=CallVerbosity.QUIET,
# type: () -> List[str]
return self.daemon_args + self.extra_args
+ @staticmethod
+ def ganesha_conf_text(conf: Union[str, List[str]]) -> str:
+ if isinstance(conf, list):
+ return '\n'.join(conf)
+ return conf
+
+ @staticmethod
+ def nfsv3_enabled_in_ganesha_conf(conf: Union[str, List[str]]) -> bool:
+ """Return True when ganesha.conf enables NFSv3 (Protocols includes 3)."""
+ text = NFSGanesha.ganesha_conf_text(conf)
+ match = re.search(r'Protocols\s*=\s*([^;]+)', text, re.MULTILINE)
+ if not match:
+ # No Protocols line (e.g. minimal test stubs): keep legacy behavior.
+ return True
+ protocols = match.group(1)
+ return bool(re.search(r'(?:^|[,\s])3(?:[,\s]|$)', protocols))
+
+ @staticmethod
+ def ganesha_entrypoint_script(nfsv3: bool = True) -> str:
+ # NFSv3 registration with SunRPC requires a running portmapper.
+ # The image installs rpcbind but cephadm previously started only
+ # ganesha.nfsd, which fails on Rocky 10 / Ganesha 9.x with
+ # "Cannot register NFS V3 on TCP" when rpcbind is not up.
+ rpcbind_block = ''
+ if nfsv3:
+ rpcbind_block = """if command -v rpcbind >/dev/null 2>&1; then
+ if ! rpcinfo -p >/dev/null 2>&1; then
+ rpcbind
+ fi
+fi
+"""
+ return f"""#!/bin/bash
+set -e
+{rpcbind_block}exec /usr/bin/ganesha.nfsd "$@"
+"""
+
def create_daemon_dirs(self, data_dir, uid, gid):
# type: (str, int, int) -> None
"""Create files under the container data dir"""
populate_files(config_dir, config_files, uid, gid)
populate_files(tls_dir, tls_files, uid, gid)
+ ganesha_conf = self.files.get('ganesha.conf', '')
+ nfsv3 = self.nfsv3_enabled_in_ganesha_conf(ganesha_conf)
+ entrypoint_path = os.path.join(data_dir, self.entrypoint_script_name)
+ with write_new(entrypoint_path, owner=(uid, gid)) as f:
+ f.write(self.ganesha_entrypoint_script(nfsv3=nfsv3))
+ os.chmod(entrypoint_path, 0o700)
+
# write the RGW keyring
if self.rgw:
keyring_path = os.path.join(data_dir, 'keyring.rgw')
good_nfs_json(),
)
cmounts = nfsg._get_container_mounts("/var/tmp")
- assert len(cmounts) == 3
+ assert len(cmounts) == 4
assert cmounts["/var/tmp/config"] == "/etc/ceph/ceph.conf:z"
assert cmounts["/var/tmp/keyring"] == "/etc/ceph/keyring:z"
assert cmounts["/var/tmp/etc/ganesha"] == "/etc/ganesha:z"
+ assert (
+ cmounts["/var/tmp/ganesha-entrypoint.sh"]
+ == "/usr/local/scripts/ganesha-entrypoint.sh"
+ )
with with_cephadm_ctx([]) as ctx:
nfsg = _cephadm.NFSGanesha(
nfs_json(pool=True, files=True, rgw=True),
)
cmounts = nfsg._get_container_mounts("/var/tmp")
- assert len(cmounts) == 4
+ assert len(cmounts) == 5
assert cmounts["/var/tmp/config"] == "/etc/ceph/ceph.conf:z"
assert cmounts["/var/tmp/keyring"] == "/etc/ceph/keyring:z"
assert cmounts["/var/tmp/etc/ganesha"] == "/etc/ganesha:z"
assert args == ["-F", "-L", "STDERR"]
+@pytest.mark.parametrize(
+ 'conf,expected',
+ [
+ ('Protocols = 3, 4;', True),
+ ('Protocols = 4, 3;', True),
+ ('Protocols = 4;', False),
+ ('Protocols = 4, nfsrdma, rpcrdma;', False),
+ ('', True),
+ ],
+)
+def test_nfsv3_enabled_in_ganesha_conf(conf, expected):
+ assert _cephadm.NFSGanesha.nfsv3_enabled_in_ganesha_conf(conf) is expected
+
+
+def test_nfsganesha_entrypoint_script_nfsv3():
+ script = _cephadm.NFSGanesha.ganesha_entrypoint_script(nfsv3=True)
+ assert 'rpcbind' in script
+ assert 'exec /usr/bin/ganesha.nfsd "$@"' in script
+
+
+def test_nfsganesha_entrypoint_script_v4_only():
+ script = _cephadm.NFSGanesha.ganesha_entrypoint_script(nfsv3=False)
+ assert 'rpcbind' not in script
+ assert 'exec /usr/bin/ganesha.nfsd "$@"' in script
+
+
+def test_nfsganesha_entrypoint_script_from_conf():
+ conf_v3 = '\n'.join(
+ [
+ 'NFS_CORE_PARAM {',
+ ' Protocols = 3, 4;',
+ '}',
+ ]
+ )
+ script = _cephadm.NFSGanesha.ganesha_entrypoint_script(
+ nfsv3=_cephadm.NFSGanesha.nfsv3_enabled_in_ganesha_conf(conf_v3),
+ )
+ assert 'rpcbind' in script
+
+ conf_v4 = 'NFS_CORE_PARAM { Protocols = 4; }'
+ script = _cephadm.NFSGanesha.ganesha_entrypoint_script(
+ nfsv3=_cephadm.NFSGanesha.nfsv3_enabled_in_ganesha_conf(conf_v4),
+ )
+ assert 'rpcbind' not in script
+
+
@mock.patch("cephadm.logger")
def test_nfsganesha_create_daemon_dirs(_logger, cephadm_fs):
with with_cephadm_ctx([]) as ctx:
nfsg.create_daemon_dirs("/var/tmp", 45, 54)
cephadm_fs.create_dir("/var/tmp")
nfsg.create_daemon_dirs("/var/tmp", 45, 54)
- # TODO: make assertions about the dirs created
+ with open("/var/tmp/ganesha-entrypoint.sh") as f:
+ assert 'rpcbind' in f.read()
+
+ nfsg_v4 = _cephadm.NFSGanesha(
+ ctx,
+ SAMPLE_UUID,
+ "fred",
+ {
+ 'pool': 'party',
+ 'files': {
+ 'ganesha.conf': 'NFS_CORE_PARAM { Protocols = 4; }',
+ 'idmap.conf': '',
+ },
+ },
+ )
+ nfsg_v4.create_daemon_dirs("/var/tmp", 45, 54)
+ with open("/var/tmp/ganesha-entrypoint.sh") as f:
+ assert 'rpcbind' not in f.read()
@mock.patch("cephadm.logger")