diff --git a/docs/source/guides/admin-guides/manage_clusters/ppc64le/statelite/config_statelite.rst b/docs/source/guides/admin-guides/manage_clusters/ppc64le/statelite/config_statelite.rst index 2c8a02d28..dcad14a7b 100644 --- a/docs/source/guides/admin-guides/manage_clusters/ppc64le/statelite/config_statelite.rst +++ b/docs/source/guides/admin-guides/manage_clusters/ppc64le/statelite/config_statelite.rst @@ -136,3 +136,16 @@ noderes ``noderes.nfsdir`` can be set. If this is not set, the default is ``/install`` +NFS root mount options +---------------------- + +For dracut-based NFS statelite images, additional NFS root mount options can +be set on the osimage. For example, to disable attribute caching:: + + chdef -t osimage -o rhels9-ppc64le-statelite-compute nfsrootopts=noac + nodeset compute osimage=rhels9-ppc64le-statelite-compute + +The value is a comma-separated list, such as ``noac,actimeo=0``. xCAT always +includes ``ro`` and does not accept ``rw`` or ``defaults`` because the shared +statelite root filesystem must remain read-only. The ``statelite.mntopts`` +attribute continues to control only the separate persistent directory. diff --git a/docs/source/guides/admin-guides/references/man5/osimage.5.rst b/docs/source/guides/admin-guides/references/man5/osimage.5.rst index b5bf84f82..510ad917c 100644 --- a/docs/source/guides/admin-guides/references/man5/osimage.5.rst +++ b/docs/source/guides/admin-guides/references/man5/osimage.5.rst @@ -19,7 +19,7 @@ SYNOPSIS ******** -\ **osimage Attributes:**\ \ *imagename*\ , \ *groups*\ , \ *profile*\ , \ *imagetype*\ , \ *description*\ , \ *provmethod*\ , \ *rootfstype*\ , \ *osdistroname*\ , \ *osupdatename*\ , \ *cfmdir*\ , \ *osname*\ , \ *osvers*\ , \ *osarch*\ , \ *synclists*\ , \ *postscripts*\ , \ *postbootscripts*\ , \ *serverrole*\ , \ *isdeletable*\ , \ *kitcomponents*\ , \ *environvar*\ , \ *comments*\ , \ *disable*\ +\ **osimage Attributes:**\ \ *imagename*\ , \ *groups*\ , \ *profile*\ , \ *imagetype*\ , \ *description*\ , \ *provmethod*\ , \ *rootfstype*\ , \ *nfsrootopts*\ , \ *osdistroname*\ , \ *osupdatename*\ , \ *cfmdir*\ , \ *osname*\ , \ *osvers*\ , \ *osarch*\ , \ *synclists*\ , \ *postscripts*\ , \ *postbootscripts*\ , \ *serverrole*\ , \ *isdeletable*\ , \ *kitcomponents*\ , \ *environvar*\ , \ *comments*\ , \ *disable* *********** @@ -78,6 +78,11 @@ osimage Attributes: +**nfsrootopts** + +A comma-separated list of additional NFS mount options for the root filesystem of a dracut-based statelite image. The root filesystem is always mounted read-only; rw and defaults are not allowed. + + \ **osdistroname**\ The name of the OS distro definition. This attribute can be used to specify which OS distro to use, instead of using the osname,osvers,and osarch attributes. For \*kit commands, the attribute will be used to read the osdistro table for the osname, osvers, and osarch attributes. If defined, the osname, osvers, and osarch attributes defined in the osimage table will be ignored. @@ -175,4 +180,3 @@ SEE ALSO \ **nodels(1)**\ , \ **chtab(8)**\ , \ **tabdump(8)**\ , \ **tabedit(8)**\ - diff --git a/perl-xCAT/xCAT/Schema.pm b/perl-xCAT/xCAT/Schema.pm index cca78aa90..63696e815 100644 --- a/perl-xCAT/xCAT/Schema.pm +++ b/perl-xCAT/xCAT/Schema.pm @@ -773,7 +773,7 @@ passed as argument rather than by table value', }, }, osimage => { - cols => [qw(imagename groups profile imagetype description provmethod rootfstype osdistroname osupdatename cfmdir osname osvers osarch synclists postscripts postbootscripts serverrole isdeletable kitcomponents environvar comments disable)], + cols => [qw(imagename groups profile imagetype description provmethod rootfstype nfsrootopts osdistroname osupdatename cfmdir osname osvers osarch synclists postscripts postbootscripts serverrole isdeletable kitcomponents environvar comments disable)], keys => [qw(imagename)], tablespace => 'XCATTBS32K', table_desc => 'Basic information about an operating system image that can be used to deploy cluster nodes.', @@ -787,6 +787,7 @@ passed as argument rather than by table value', description => 'OS Image Description', provmethod => 'The provisioning method for node deployment. The valid values are install, netboot,statelite,boottarget,dualboot,sysclone. If boottarget is set, you must set linuximage.boottarget to the name of the boottarget definition. It is not used by AIX.', rootfstype => 'The filesystem type for the rootfs is used when the provmethod is statelite. The valid values are nfs or ramdisk. The default value is nfs', + nfsrootopts => 'A comma-separated list of additional NFS mount options for the root filesystem of a dracut-based statelite image. The root filesystem is always mounted read-only; rw and defaults are not allowed.', osdistroname => 'The name of the OS distro definition. This attribute can be used to specify which OS distro to use, instead of using the osname,osvers,and osarch attributes. For *kit commands, the attribute will be used to read the osdistro table for the osname, osvers, and osarch attributes. If defined, the osname, osvers, and osarch attributes defined in the osimage table will be ignored.', osupdatename => 'A comma-separated list of OS distro updates to apply to this osimage.', cfmdir => 'CFM directory name for PCM. Set to /install/osimages//cfmdir by PCM. ', @@ -3207,6 +3208,11 @@ push(@{ $defspec{node}->{'attrs'} }, @nodeattrs); tabentry => 'osimage.rootfstype', access_tabentry => 'osimage.imagename=attr:imagename', }, + { attr_name => 'nfsrootopts', + only_if => 'imagetype=linux', + tabentry => 'osimage.nfsrootopts', + access_tabentry => 'osimage.imagename=attr:imagename', + }, { attr_name => 'profile', tabentry => 'osimage.profile', access_tabentry => 'osimage.imagename=attr:imagename', diff --git a/xCAT-server/lib/perl/xCAT/SvrUtils.pm b/xCAT-server/lib/perl/xCAT/SvrUtils.pm index 92e615543..04b7305c8 100644 --- a/xCAT-server/lib/perl/xCAT/SvrUtils.pm +++ b/xCAT-server/lib/perl/xCAT/SvrUtils.pm @@ -2415,6 +2415,32 @@ sub getplatform { return $platform; } +# Build the dracut root=nfs parameter while preserving statelite's read-only +# root filesystem contract. +sub build_statelite_nfsroot_parameter { + shift if @_ && defined($_[0]) && $_[0] eq __PACKAGE__; + my ($server, $rootdir, $additional_options) = @_; + + my @mount_options = ('ro'); + if (defined($additional_options) && $additional_options ne '') { + foreach my $option (split(/,/, $additional_options, -1)) { + if ($option eq '' || $option =~ /\s/) { + return (undef, 'nfsrootopts must be a comma-separated list without whitespace or empty options'); + } + + my ($name) = split(/=/, $option, 2); + $name = lc($name); + if ($name eq 'rw' || $name eq 'defaults') { + return (undef, "nfsrootopts cannot contain '$name'; statelite NFS roots are always read-only"); + } + next if lc($option) eq 'ro'; + push @mount_options, $option; + } + } + + return ('root=nfs:' . $server . ':' . $rootdir . ':' . join(',', @mount_options), undef); +} + #-------------------------------------------------------------------------------------------------------- #searchcompressedrootimg: #description: search the compressed rootimage for diskless or statelite osimage under specified directory diff --git a/xCAT-server/lib/xcat/plugins/anaconda.pm b/xCAT-server/lib/xcat/plugins/anaconda.pm index 7a4c0f7f5..0187aef47 100644 --- a/xCAT-server/lib/xcat/plugins/anaconda.pm +++ b/xCAT-server/lib/xcat/plugins/anaconda.pm @@ -261,6 +261,7 @@ sub mknetboot my $dump; # for kdump, its format is "nfs:///" my $crashkernelsize; my $rootfstype; + my $nfsrootopts; my $tftpdir; my $cfgpart; my $imagename; # set it if running of 'nodeset osimage=xxx' @@ -283,13 +284,14 @@ sub mknetboot if (!$osimagetab) { $osimagetab = xCAT::Table->new('osimage', -create => 1); } - (my $ref) = $osimagetab->getAttribs({ imagename => $imagename }, 'osvers', 'osarch', 'profile', 'provmethod', 'rootfstype'); + (my $ref) = $osimagetab->getAttribs({ imagename => $imagename }, 'osvers', 'osarch', 'profile', 'provmethod', 'rootfstype', 'nfsrootopts'); if ($ref) { $img_hash{$imagename}->{osver} = $ref->{'osvers'}; $img_hash{$imagename}->{osarch} = $ref->{'osarch'}; $img_hash{$imagename}->{profile} = $ref->{'profile'}; $img_hash{$imagename}->{provmethod} = $ref->{'provmethod'}; $img_hash{$imagename}->{rootfstype} = $ref->{rootfstype}; + $img_hash{$imagename}->{nfsrootopts} = $ref->{nfsrootopts}; if (!$linuximagetab) { $linuximagetab = xCAT::Table->new('linuximage', -create => 1); } @@ -343,6 +345,7 @@ sub mknetboot $profile = $ph->{profile}; $rootfstype = $ph->{rootfstype}; + $nfsrootopts = $ph->{nfsrootopts}; $rootimgdir = $ph->{rootimgdir}; unless ($rootimgdir) { $rootimgdir = "$installroot/netboot/$osver/$arch/$profile"; @@ -382,9 +385,10 @@ sub mknetboot } if ($osimagetab) { - my ($ref1) = $osimagetab->getAttribs({ imagename => $imgname }, 'rootfstype'); - if (($ref1) && ($ref1->{'rootfstype'})) { - $rootfstype = $ref1->{'rootfstype'}; + my ($ref1) = $osimagetab->getAttribs({ imagename => $imgname }, 'rootfstype', 'nfsrootopts'); + if ($ref1) { + $rootfstype = $ref1->{'rootfstype'} if $ref1->{'rootfstype'}; + $nfsrootopts = $ref1->{'nfsrootopts'}; } } else { xCAT::MsgUtils->report_node_error($callback, $node, @@ -634,7 +638,12 @@ sub mknetboot # special case for redhat6, fedora12/13/14 if (&using_dracut($osver)) { - $kcmdline = "root=nfs:$nfssrv:$nfsdir/rootimg:ro STATEMNT="; + my ($nfsroot, $error) = xCAT::SvrUtils->build_statelite_nfsroot_parameter($nfssrv, "$nfsdir/rootimg", $nfsrootopts); + if ($error) { + xCAT::MsgUtils->report_node_error($callback, $node, $error); + next; + } + $kcmdline = "$nfsroot STATEMNT="; } else { $kcmdline = "NFSROOT=$nfssrv:$nfsdir STATEMNT="; } diff --git a/xCAT-server/lib/xcat/plugins/debian.pm b/xCAT-server/lib/xcat/plugins/debian.pm index 523cbfa9e..113414b85 100644 --- a/xCAT-server/lib/xcat/plugins/debian.pm +++ b/xCAT-server/lib/xcat/plugins/debian.pm @@ -1163,6 +1163,7 @@ sub mknetboot my $dump; # for kdump, its format is "nfs:///" my $crashkernelsize; my $rootfstype; + my $nfsrootopts; my $tftpdir; my $imagename; # set it if running of 'nodeset osimage=xxx' @@ -1179,13 +1180,14 @@ sub mknetboot if (!$osimagetab) { $osimagetab = xCAT::Table->new('osimage', -create => 1); } - (my $ref) = $osimagetab->getAttribs({ imagename => $imagename }, 'osvers', 'osarch', 'profile', 'provmethod', 'rootfstype'); + (my $ref) = $osimagetab->getAttribs({ imagename => $imagename }, 'osvers', 'osarch', 'profile', 'provmethod', 'rootfstype', 'nfsrootopts'); if ($ref) { $img_hash{$imagename}->{osver} = $ref->{'osvers'}; $img_hash{$imagename}->{osarch} = $ref->{'osarch'}; $img_hash{$imagename}->{profile} = $ref->{'profile'}; $img_hash{$imagename}->{provmethod} = $ref->{'provmethod'}; $img_hash{$imagename}->{rootfstype} = $ref->{'rootfstype'}; + $img_hash{$imagename}->{nfsrootopts} = $ref->{'nfsrootopts'}; if (!$linuximagetab) { $linuximagetab = xCAT::Table->new('linuximage', -create => 1); } @@ -1215,6 +1217,7 @@ sub mknetboot $profile = $ph->{profile}; $rootfstype = $ph->{rootfstype}; + $nfsrootopts = $ph->{nfsrootopts}; $rootimgdir = $ph->{rootimgdir}; unless ($rootimgdir) { @@ -1249,9 +1252,10 @@ sub mknetboot } if ($osimagetab) { - my ($ref1) = $osimagetab->getAttribs({ imagename => $imgname }, 'rootfstype'); - if (($ref1) && ($ref1->{'rootfstype'})) { - $rootfstype = $ref1->{'rootfstype'}; + my ($ref1) = $osimagetab->getAttribs({ imagename => $imgname }, 'rootfstype', 'nfsrootopts'); + if ($ref1) { + $rootfstype = $ref1->{'rootfstype'} if $ref1->{'rootfstype'}; + $nfsrootopts = $ref1->{'nfsrootopts'}; } } else { xCAT::MsgUtils->report_node_error($callback, $node, @@ -1456,7 +1460,12 @@ sub mknetboot # special case for redhat6, fedora12/13/14 if (&using_dracut($osver)) { - $kcmdline = "root=nfs:$nfssrv:$nfsdir/rootimg:ro STATEMNT="; + my ($nfsroot, $error) = xCAT::SvrUtils->build_statelite_nfsroot_parameter($nfssrv, "$nfsdir/rootimg", $nfsrootopts); + if ($error) { + xCAT::MsgUtils->report_node_error($callback, $node, $error); + next; + } + $kcmdline = "$nfsroot STATEMNT="; } else { $kcmdline = "NFSROOT=$nfssrv:$nfsdir STATEMNT="; } diff --git a/xCAT-server/lib/xcat/plugins/sles.pm b/xCAT-server/lib/xcat/plugins/sles.pm index 24cadae36..e2ac19691 100644 --- a/xCAT-server/lib/xcat/plugins/sles.pm +++ b/xCAT-server/lib/xcat/plugins/sles.pm @@ -137,6 +137,7 @@ sub mknetboot my $dump; #for kdump my $crashkernelsize; my $rootfstype; + my $nfsrootopts; my $cfgpart; my $imagename; # set it if running of 'nodeset osimage=xxx' @@ -147,12 +148,13 @@ sub mknetboot if (!$osimagetab) { $osimagetab = xCAT::Table->new('osimage', -create => 1); } - (my $ref) = $osimagetab->getAttribs({ imagename => $imagename }, 'osvers', 'osarch', 'profile', 'rootfstype', 'provmethod'); + (my $ref) = $osimagetab->getAttribs({ imagename => $imagename }, 'osvers', 'osarch', 'profile', 'rootfstype', 'nfsrootopts', 'provmethod'); if ($ref) { $img_hash{$imagename}->{osver} = $ref->{'osvers'}; $img_hash{$imagename}->{osarch} = $ref->{'osarch'}; $img_hash{$imagename}->{profile} = $ref->{'profile'}; $img_hash{$imagename}->{rootfstype} = $ref->{'rootfstype'}; + $img_hash{$imagename}->{nfsrootopts} = $ref->{'nfsrootopts'}; $img_hash{$imagename}->{provmethod} = $ref->{'provmethod'}; if (!$linuximagetab) { $linuximagetab = xCAT::Table->new('linuximage', -create => 1); @@ -204,6 +206,7 @@ sub mknetboot $arch = $ph->{osarch}; $profile = $ph->{profile}; $rootfstype = $ph->{rootfstype}; + $nfsrootopts = $ph->{nfsrootopts}; $nodebootif = $ph->{nodebootif}; $provmethod = $ph->{provmethod}; $dump = $ph->{dump}; @@ -242,9 +245,10 @@ sub mknetboot } if ($osimagetab) { - my ($ref1) = $osimagetab->getAttribs({ imagename => $imgname }, 'rootfstype'); - if (($ref1) && ($ref1->{'rootfstype'})) { - $rootfstype = $ref1->{'rootfstype'}; + my ($ref1) = $osimagetab->getAttribs({ imagename => $imgname }, 'rootfstype', 'nfsrootopts'); + if ($ref1) { + $rootfstype = $ref1->{'rootfstype'} if $ref1->{'rootfstype'}; + $nfsrootopts = $ref1->{'nfsrootopts'}; } } else { xCAT::MsgUtils->report_node_error($callback, $node, @@ -469,7 +473,12 @@ sub mknetboot $nfsdir = $ient->{nfsdir} . "/netboot/$osver/$arch/$profile"; } if (&using_dracut($rootimgdir)) { - $kcmdline = "root=nfs:$nfssrv:$nfsdir/rootimg:ro STATEMNT="; + my ($nfsroot, $error) = xCAT::SvrUtils->build_statelite_nfsroot_parameter($nfssrv, "$nfsdir/rootimg", $nfsrootopts); + if ($error) { + xCAT::MsgUtils->report_node_error($callback, $node, $error); + next; + } + $kcmdline = "$nfsroot STATEMNT="; } else { $kcmdline = "NFSROOT=$nfssrv:$nfsdir STATEMNT="; } diff --git a/xCAT-test/unit/nfs_root_options.t b/xCAT-test/unit/nfs_root_options.t new file mode 100644 index 000000000..99391f85b --- /dev/null +++ b/xCAT-test/unit/nfs_root_options.t @@ -0,0 +1,62 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../../xCAT-server/lib/perl"; +use lib "$FindBin::Bin/../../perl-xCAT"; + +use Test::More; +use xCAT::Schema; +use xCAT::SvrUtils; + +sub build_nfsroot { + return xCAT::SvrUtils->build_statelite_nfsroot_parameter('192.0.2.1', '/install/netboot/test/rootimg', shift); +} + +my ($parameter, $error) = build_nfsroot(undef); +is($parameter, 'root=nfs:192.0.2.1:/install/netboot/test/rootimg:ro', 'unset options preserve the read-only default'); +is($error, undef, 'unset options are valid'); + +($parameter, $error) = build_nfsroot(''); +is($parameter, 'root=nfs:192.0.2.1:/install/netboot/test/rootimg:ro', 'empty options preserve the read-only default'); +is($error, undef, 'empty options are valid'); + +($parameter, $error) = build_nfsroot('noac,actimeo=0'); +is($parameter, 'root=nfs:192.0.2.1:/install/netboot/test/rootimg:ro,noac,actimeo=0', 'additional options follow the mandatory read-only option'); +is($error, undef, 'valid additional options are accepted'); + +($parameter, $error) = build_nfsroot('ro,nfsvers=4.1'); +is($parameter, 'root=nfs:192.0.2.1:/install/netboot/test/rootimg:ro,nfsvers=4.1', 'a redundant read-only option is normalized'); +is($error, undef, 'a redundant read-only option is valid'); + +($parameter, $error) = build_nfsroot('clientaddr=2001:db8::1'); +is($parameter, 'root=nfs:192.0.2.1:/install/netboot/test/rootimg:ro,clientaddr=2001:db8::1', 'option values may contain colons'); +is($error, undef, 'an option value containing colons is valid'); + +foreach my $invalid ('rw', 'RW', 'noac,rw', 'defaults', 'noac, actimeo=0', 'noac,,actimeo=0') { + ($parameter, $error) = build_nfsroot($invalid); + is($parameter, undef, "invalid option list '$invalid' is rejected"); + like($error, qr/^nfsrootopts /, "invalid option list '$invalid' reports the attribute name"); +} + +ok( + scalar(grep { $_ eq 'nfsrootopts' } @{ $xCAT::Schema::tabspec{osimage}->{cols} }), + 'osimage table includes nfsrootopts' +); +is( + $xCAT::Schema::defspec{osimage}->{attrhash}->{nfsrootopts}->{tabentry}, + 'osimage.nfsrootopts', + 'osimage object exposes nfsrootopts' +); + +foreach my $plugin (qw(anaconda debian sles)) { + my $path = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/$plugin.pm"; + open(my $fh, '<', $path) or die "Unable to read $path: $!"; + my $source = do { local $/; <$fh> }; + close($fh); + like($source, qr/build_statelite_nfsroot_parameter/, "$plugin statelite renderer uses the shared NFS-root builder"); + like($source, qr/'rootfstype',\s*'nfsrootopts'/, "$plugin reads nfsrootopts with the statelite root type"); +} + +done_testing();