diff --git a/xCAT-server/lib/perl/xCAT/SvrUtils.pm b/xCAT-server/lib/perl/xCAT/SvrUtils.pm index b15aa3002..a4440e7b3 100644 --- a/xCAT-server/lib/perl/xCAT/SvrUtils.pm +++ b/xCAT-server/lib/perl/xCAT/SvrUtils.pm @@ -1693,6 +1693,45 @@ sub nfs_export_exists { return 0; } +#----------------------------------------------------------------------------- + +=head3 nfs_export_line + + The /etc/exports line for a directory a service node serves to its nodes. + + Arguments: + $dir the directory to export + reexport true when $dir is itself an NFS mount + + Returns: + one export line, with no trailing newline. + + A local directory keeps the options xCAT has always written. Re-exporting an NFS mount needs + two more: the kernel cannot derive a filesystem id for one, so exportfs refuses it without an + explicit fsid, and crossmnt lets a client cross into the mount below. The fsid is derived + from the path so that it survives a restart -- a new one would make every client's mount + stale. + +=cut + +#----------------------------------------------------------------------------- +sub nfs_export_line { + my ( $dir, %opts ) = _nfs_method_args(@_); + my $options = 'rw,no_root_squash,sync,no_subtree_check,insecure'; + if ( $opts{reexport} ) { + $options .= ',crossmnt,fsid=' . _nfs_export_fsid($dir); + } + return "$dir *($options)"; +} + +# A stable, non-zero fsid for a path. 0 is reserved for the export root. +sub _nfs_export_fsid { + my ($dir) = @_; + my $sum = 0; + $sum = ( $sum * 31 + ord($_) ) % 2147483647 for split //, $dir; + return $sum || 1; +} + sub _ensure_nfs_exported { my ($nfsserver, $nfsdirectory, $callback, %opts) = @_; my $export_options = 'rw,no_root_squash,sync,no_subtree_check,insecure'; diff --git a/xCAT-server/lib/xcat/plugins/AAsn.pm b/xCAT-server/lib/xcat/plugins/AAsn.pm index 188d7ebdc..bc5350ccf 100644 --- a/xCAT-server/lib/xcat/plugins/AAsn.pm +++ b/xCAT-server/lib/xcat/plugins/AAsn.pm @@ -919,6 +919,24 @@ sub setup_NFS my $rc = 0; if (xCAT::Utils->isLinux()) { + # servicenode.nfsserver=1 means this node serves files to the nodes behind it. With + # site.installloc set it MOUNTS the install directory from the management node and the + # caller exports nothing, so the node served no NFS at all: an Ubuntu compute node stops + # at "Unable to find a live file system on the network", because casper mounts the + # install tree over NFS from its own service node. Exporting a mount needs an fsid, which + # nfs_export_line adds. + my $installdir = xCAT::TableUtils->getInstallDir() || "/install"; + unless (xCAT::SvrUtils->nfs_export_exists($installdir)) + { + my $line = xCAT::SvrUtils->nfs_export_line($installdir, + reexport => (xCAT::Utils->isMounted($installdir) ? 1 : 0)); + xCAT::Utils->runcmd("/bin/echo '$line' >> /etc/exports", 0); + if ($::RUNCMD_RC != 0) + { + xCAT::MsgUtils->message('S', "Could not add $installdir to /etc/exports."); + } + } + #my $os = xCAT::Utils->osver(); #if ($os =~ /sles.*/) #{ @@ -930,6 +948,14 @@ sub setup_NFS # $rc = xCAT::Utils->startService("nfs"); #} $rc = xCAT::Utils->startservice("nfs"); + + # After the daemon, so a fresh export is picked up. An export the kernel refuses is + # reported and does not fail the service node, which has other services to set up. + xCAT::Utils->runcmd("/usr/sbin/exportfs -a", 0); + if ($::RUNCMD_RC != 0) + { + xCAT::MsgUtils->message('S', "Error with /usr/sbin/exportfs -a."); + } } else { #AIX diff --git a/xCAT-test/unit/svrutils_nfs_export_line.t b/xCAT-test/unit/svrutils_nfs_export_line.t new file mode 100644 index 000000000..3adbfcbfc --- /dev/null +++ b/xCAT-test/unit/svrutils_nfs_export_line.t @@ -0,0 +1,52 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use Test::More; + +# A service node with servicenode.nfsserver=1 is asked to "set up file services on this service +# node". When site.installloc is set it MOUNTS /install from the management node, and AAsn.pm +# then exported nothing at all, so the node served no NFS. An Ubuntu compute node boots the live +# installer with casper, which mounts the install tree over NFS from its own service node, and it +# stopped at "Unable to find a live file system on the network". Measured on xcat22-sn: +# showmount -e returned an empty list and /etc/exports was empty while nfs-kernel-server was +# active. +# +# Re-exporting an NFS mount is not the same as exporting a local directory. Linux requires an +# explicit fsid, because it cannot derive one from the underlying filesystem, and refuses the +# export without it. + +use lib "$FindBin::Bin/../../perl-xCAT"; +use lib "$FindBin::Bin/../../xCAT-server/lib/perl"; +use xCAT::SvrUtils; + +can_ok('xCAT::SvrUtils', 'nfs_export_line') or done_testing() && exit; + +my $local = xCAT::SvrUtils->nfs_export_line('/install'); +is($local, '/install *(rw,no_root_squash,sync,no_subtree_check,insecure)', + 'a local directory keeps the options xCAT has always written'); + +my $reexport = xCAT::SvrUtils->nfs_export_line('/install', reexport => 1); +like($reexport, qr{^/install \*\(}, 're-export names the same directory and clients'); +like($reexport, qr{\brw\b}, '... stays read-write'); +like($reexport, qr{\bno_root_squash\b}, '... keeps no_root_squash, which the installer needs'); +like($reexport, qr{\binsecure\b}, '... keeps insecure, for clients on high ports'); +like($reexport, qr{\bfsid=\d+}, '... carries an fsid, without which exportfs refuses an NFS mount'); +like($reexport, qr{\bcrossmnt\b}, '... carries crossmnt, so the mount below it is followed'); + +# The fsid must be stable across runs, or every restart hands clients a new filesystem identity +# and their mounts go stale. +is(xCAT::SvrUtils->nfs_export_line('/install', reexport => 1), $reexport, + 'the same directory yields the same fsid every time'); + +my ($install_fsid) = $reexport =~ /fsid=(\d+)/; +my ($tftp_fsid) = xCAT::SvrUtils->nfs_export_line('/tftpboot', reexport => 1) =~ /fsid=(\d+)/; +isnt($tftp_fsid, $install_fsid, 'two directories do not share one fsid'); +cmp_ok($install_fsid, '>', 0, 'the fsid is not 0, which Linux reserves for the export root'); +# The guard that keeps it non-zero is only reachable when the checksum itself is 0, which an +# empty path produces. Drive it directly rather than claim a path that cannot reach it. +cmp_ok(xCAT::SvrUtils::_nfs_export_fsid(''), '>', 0, + 'a path whose checksum is 0 still yields a usable fsid'); + +done_testing();