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..5ec6a150e 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/docs/source/guides/admin-guides/references/man7/osimage.7.rst b/docs/source/guides/admin-guides/references/man7/osimage.7.rst index 1171e280f..cb31b767e 100644 --- a/docs/source/guides/admin-guides/references/man7/osimage.7.rst +++ b/docs/source/guides/admin-guides/references/man7/osimage.7.rst @@ -19,7 +19,7 @@ SYNOPSIS ******** -\ **osimage Attributes:**\ \ *addkcmdline*\ , \ *boottarget*\ , \ *bosinst_data*\ , \ *cfmdir*\ , \ *configdump*\ , \ *crashkernelsize*\ , \ *description*\ , \ *driverupdatesrc*\ , \ *dump*\ , \ *environvar*\ , \ *exlist*\ , \ *fb_script*\ , \ *groups*\ , \ *home*\ , \ *image_data*\ , \ *imagename*\ , \ *imagetype*\ , \ *installp_bundle*\ , \ *installto*\ , \ *isdeletable*\ , \ *kerneldir*\ , \ *kernelver*\ , \ *kitcomponents*\ , \ *krpmver*\ , \ *lpp_source*\ , \ *mksysb*\ , \ *netdrivers*\ , \ *nimmethod*\ , \ *nimtype*\ , \ *nodebootif*\ , \ *osarch*\ , \ *osdistroname*\ , \ *osname*\ , \ *osupdatename*\ , \ *osvers*\ , \ *otherifce*\ , \ *otherpkgdir*\ , \ *otherpkglist*\ , \ *otherpkgs*\ , \ *paging*\ , \ *partitionfile*\ , \ *permission*\ , \ *pkgdir*\ , \ *pkglist*\ , \ *postbootscripts*\ , \ *postinstall*\ , \ *postscripts*\ , \ *profile*\ , \ *provmethod*\ , \ *resolv_conf*\ , \ *root*\ , \ *rootfstype*\ , \ *rootimgdir*\ , \ *script*\ , \ *serverrole*\ , \ *shared_home*\ , \ *shared_root*\ , \ *spot*\ , \ *synclists*\ , \ *template*\ , \ *tmp*\ , \ *usercomment*\ , \ *winpepath*\ +\ **osimage Attributes:**\ \ *addkcmdline*\ , \ *boottarget*\ , \ *bosinst_data*\ , \ *cfmdir*\ , \ *configdump*\ , \ *crashkernelsize*\ , \ *description*\ , \ *driverupdatesrc*\ , \ *dump*\ , \ *environvar*\ , \ *exlist*\ , \ *fb_script*\ , \ *groups*\ , \ *home*\ , \ *image_data*\ , \ *imagename*\ , \ *imagetype*\ , \ *installp_bundle*\ , \ *installto*\ , \ *isdeletable*\ , \ *kerneldir*\ , \ *kernelver*\ , \ *kitcomponents*\ , \ *krpmver*\ , \ *lpp_source*\ , \ *mksysb*\ , \ *netdrivers*\ , \ *nfsrootopts*\ , \ *nimmethod*\ , \ *nimtype*\ , \ *nodebootif*\ , \ *osarch*\ , \ *osdistroname*\ , \ *osname*\ , \ *osupdatename*\ , \ *osvers*\ , \ *otherifce*\ , \ *otherpkgdir*\ , \ *otherpkglist*\ , \ *otherpkgs*\ , \ *paging*\ , \ *partitionfile*\ , \ *permission*\ , \ *pkgdir*\ , \ *pkglist*\ , \ *postbootscripts*\ , \ *postinstall*\ , \ *postscripts*\ , \ *profile*\ , \ *provmethod*\ , \ *resolv_conf*\ , \ *root*\ , \ *rootfstype*\ , \ *rootimgdir*\ , \ *script*\ , \ *serverrole*\ , \ *shared_home*\ , \ *shared_root*\ , \ *spot*\ , \ *synclists*\ , \ *template*\ , \ *tmp*\ , \ *usercomment*\ , \ *winpepath* *********** @@ -204,6 +204,11 @@ osimage Attributes: The ethernet device drivers of the nodes which will use this linux image, at least the device driver for the nodes' installnic should be included +\ **nfsrootopts**\ (osimage.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. + + \ **nimmethod**\ (nimimage.nimmethod) diff --git a/perl-xCAT/xCAT/Schema.pm b/perl-xCAT/xCAT/Schema.pm index 7f1578a3b..8f3c9d6e4 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. ', @@ -3217,6 +3218,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..e1a8df247 100644 --- a/xCAT-server/lib/perl/xCAT/SvrUtils.pm +++ b/xCAT-server/lib/perl/xCAT/SvrUtils.pm @@ -2415,6 +2415,54 @@ sub getplatform { return $platform; } +# Parse statelite NFS root options once so command-time validation and the +# dracut root=nfs renderer enforce the same rules. +sub _parse_statelite_nfsroot_options { + my ($additional_options) = @_; + + my @mount_options; + 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 (\@mount_options, undef); +} + +# Return an error message when statelite NFS root options violate the +# read-only root filesystem contract. +sub validate_statelite_nfsroot_options { + shift if @_ && defined($_[0]) && $_[0] eq __PACKAGE__; + my ($additional_options) = @_; + + my ($mount_options, $error) = _parse_statelite_nfsroot_options($additional_options); + return $error; +} + +# 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 ($additional_mount_options, $error) = _parse_statelite_nfsroot_options($additional_options); + return (undef, $error) if $error; + + my @mount_options = ('ro', @{$additional_mount_options}); + 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/DBobjectdefs.pm b/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm index c9c41cb31..6c154768a 100644 --- a/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm +++ b/xCAT-server/lib/xcat/plugins/DBobjectdefs.pm @@ -369,6 +369,19 @@ sub parse_attr_for_osimage { return 0; } +sub validate_osimage_nfsrootopts { + my $attr_hash = shift; + return 0 unless exists($attr_hash->{nfsrootopts}); + + my $validation_error = xCAT::SvrUtils->validate_statelite_nfsroot_options($attr_hash->{nfsrootopts}); + return 0 unless $validation_error; + + my $rsp; + $rsp->{data}->[0] = $validation_error; + xCAT::MsgUtils->message("E", $rsp, $::callback); + return 1; +} + #---------------------------------------------------------------------------- =head3 processArgs @@ -1886,6 +1899,13 @@ sub defmk next; } + if (($type eq 'osimage') && validate_osimage_nfsrootopts($::FINALATTRS{$obj})) + { + $error = 1; + delete $::FINALATTRS{$obj}; + next; + } + if ($object_exists && $::opt_f) { # remove the old object after the replacement definition is known valid @@ -2531,6 +2551,13 @@ sub defch $isDefined = 1; } + if (($type eq 'osimage') && !$::opt_m && validate_osimage_nfsrootopts($::FINALATTRS{$obj})) + { + $error = 1; + delete($::FINALATTRS{$obj}); + next; + } + if ($type eq 'network') { my $isInvalid = 0; diff --git a/xCAT-server/lib/xcat/plugins/anaconda.pm b/xCAT-server/lib/xcat/plugins/anaconda.pm index 7a4c0f7f5..6ad77d3b3 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}->{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..4051ca18e 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}->{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 a71dd175e..ec35e0f15 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,13 +148,14 @@ 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}->{provmethod} = $ref->{'provmethod'}; + $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/autotest/testcase/chdef/cases0 b/xCAT-test/autotest/testcase/chdef/cases0 index dc84f1e40..e2456c164 100644 --- a/xCAT-test/autotest/testcase/chdef/cases0 +++ b/xCAT-test/autotest/testcase/chdef/cases0 @@ -34,6 +34,32 @@ cmd:rmdef -t node testnode check:rc==0 end +start:chdef_osimage_nfsrootopts_validation +description:Reject invalid statelite NFS root options before provisioning without changing the osimage +label:mn_only,ci_test,db,statelite,nfsrootopts +cmd:rmdef -t node -o xcatnfsoptscinode >/dev/null 2>&1; rmdef -t osimage -o xcatnfsoptsciimage >/dev/null 2>&1; rm -rf /tmp/xcat-nfsrootopts-ci-root /tftpboot/xcat/osimage/xcatnfsoptsciimage; rm -f /tftpboot/xcat/xnba/nodes/xcatnfsoptscinode* +cmd:mkdir -p /tmp/xcat-nfsrootopts-ci-root; touch /tmp/xcat-nfsrootopts-ci-root/kernel /tmp/xcat-nfsrootopts-ci-root/initrd-statelite.gz +check:rc==0 +cmd:mkdef -t osimage -o xcatnfsoptsciimage imagetype=linux provmethod=statelite profile=compute osname=Linux osvers=rhels6.99 osarch=x86_64 rootfstype=nfs rootimgdir=/tmp/xcat-nfsrootopts-ci-root nfsrootopts=noac +check:rc==0 +cmd:mkdef -t node -o xcatnfsoptscinode groups=all arch=x86_64 netboot=xnba os=rhels6.99 profile=compute ip=192.0.2.100 mac=02:00:00:00:00:01 +check:rc==0 +cmd:chdef -t osimage -o xcatnfsoptsciimage nfsrootopts=rw +check:rc!=0 +check:output=~nfsrootopts cannot contain 'rw'; statelite NFS roots are always read-only +cmd:lsdef -t osimage -o xcatnfsoptsciimage -i nfsrootopts +check:rc==0 +check:output=~nfsrootopts=noac +check:output!~nfsrootopts=rw +cmd:old=`lsdef -t site clustersite -i dhcpsetup | grep 'dhcpsetup=' | awk -F= '{print $2}'`; trap 'chdef -t site clustersite dhcpsetup="$old" >/dev/null 2>&1' EXIT; chdef -t site clustersite dhcpsetup=n >/dev/null 2>&1 && nodeset xcatnfsoptscinode osimage=xcatnfsoptsciimage +check:rc==0 +check:output!~nfsrootopts +cmd:grep 'root=nfs:.*:ro,noac' /tftpboot/xcat/xnba/nodes/xcatnfsoptscinode +check:rc==0 +cmd:rmdef -t node -o xcatnfsoptscinode; rmdef -t osimage -o xcatnfsoptsciimage; rm -rf /tmp/xcat-nfsrootopts-ci-root /tftpboot/xcat/osimage/xcatnfsoptsciimage; rm -f /tftpboot/xcat/xnba/nodes/xcatnfsoptscinode* +check:rc==0 +end + start:chdef_t_network description:chdef -t network label:mn_only,ci_test,db @@ -324,4 +350,3 @@ cmd:rmdef -t network -o aaaaa_not_exist check:rc==0 cmd:rm -rf /tmp/bbbbb_not_exist.def end - diff --git a/xCAT-test/autotest/testcase/installation/reg_linux_statelite_installation_hierarchy_by_nfs b/xCAT-test/autotest/testcase/installation/reg_linux_statelite_installation_hierarchy_by_nfs index 637386df4..c73fd3e30 100644 --- a/xCAT-test/autotest/testcase/installation/reg_linux_statelite_installation_hierarchy_by_nfs +++ b/xCAT-test/autotest/testcase/installation/reg_linux_statelite_installation_hierarchy_by_nfs @@ -65,8 +65,15 @@ check:rc==0 cmd:lsdef -t osimage -o __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-statelite-compute check:rc==0 -cmd:chdef -t osimage -o __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-statelite-compute rootfstype=nfs +cmd:chdef -t osimage -o __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-statelite-compute rootfstype=nfs nfsrootopts=noac check:rc==0 +cmd:chdef -t osimage -o __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-statelite-compute nfsrootopts=rw +check:rc!=0 +check:output=~nfsrootopts cannot contain 'rw'; statelite NFS roots are always read-only +cmd:lsdef -t osimage -o __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-statelite-compute -i nfsrootopts +check:rc==0 +check:output=~nfsrootopts=noac +check:output!~nfsrootopts=rw cmd:genimage __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-statelite-compute check:rc==0 @@ -99,6 +106,8 @@ cmd:xdsh $$CN mount check:rc==0 check:output=~/nodedata/$$CN on /.statelite/persistent check:output=~compute/rootimg on / type nfs +cmd:rootimgdir=`lsdef -t osimage __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-statelite-compute|grep rootimgdir|awk -F'=' '{print $2}'`; if [[ "__GETNODEATTR($$CN,os)__" =~ ^(rhels|rhel|centos|alma|rocky|ol)([6-9]|10) ]] || [[ "__GETNODEATTR($$CN,os)__" =~ ^fedora(1[2-9]|[2-9][0-9]) ]] || [[ "__GETNODEATTR($$CN,os)__" =~ ^SL([6-9]|10) ]] || [ -f "$rootimgdir/rootimg/etc/dracut.conf" ]; then xdsh $$CN mount | grep 'compute/rootimg on / type nfs' | grep 'noac'; fi +check:rc==0 cmd:xdsh $$CN hostname check:rc==0 check:output=~$$CN: $$CN @@ -112,6 +121,8 @@ check:output=~No load hostkey warning cmd:if [ -x /usr/bin/goconserver ]; then makegocons -d $$CN; else makeconservercf -d $$CN; fi cmd:output=$(xdsh $$CN ls -al / |grep test.statelite);if [[ $? -eq 0 ]];then xdsh $$CN rm -rf /test.statelite;fi +cmd:chdef -t osimage -o __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-statelite-compute nfsrootopts= +check:rc==0 cmd:rootimgdir=`lsdef -t osimage __GETNODEATTR($$CN,os)__-__GETNODEATTR($$CN,arch)__-statelite-compute|grep rootimgdir|awk -F'=' '{print $2}'`; if [ -d $rootimgdir ]; then rm -rf $rootimgdir;fi check:rc==0 end diff --git a/xCAT-test/unit/nfs_root_options.t b/xCAT-test/unit/nfs_root_options.t new file mode 100644 index 000000000..bc721adc0 --- /dev/null +++ b/xCAT-test/unit/nfs_root_options.t @@ -0,0 +1,71 @@ +#!/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"); + like( + xCAT::SvrUtils->validate_statelite_nfsroot_options($invalid), + qr/^nfsrootopts /, + "command-time validation rejects '$invalid'" + ); +} + +is(xCAT::SvrUtils->validate_statelite_nfsroot_options(undef), undef, 'command-time validation accepts an unset value'); +is(xCAT::SvrUtils->validate_statelite_nfsroot_options(''), undef, 'command-time validation accepts an empty value'); +is(xCAT::SvrUtils->validate_statelite_nfsroot_options('noac,actimeo=0'), undef, 'command-time validation accepts valid options'); + +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();