diff --git a/xCAT-server/lib/xcat/plugins/mknb.pm b/xCAT-server/lib/xcat/plugins/mknb.pm index a3a2a5b91..94702fe66 100644 --- a/xCAT-server/lib/xcat/plugins/mknb.pm +++ b/xCAT-server/lib/xcat/plugins/mknb.pm @@ -18,6 +18,14 @@ sub _canonical_genesis_arch { return $arch eq 'ppc64el' ? 'ppc64le' : $arch; } +# Architectures whose discovery boot goes through UEFI firmware and grub2. +# mknb writes one grub2 configuration per network for them. The value is the +# $grub_cpu string reported by that architecture's GRUB build, so one file can +# carry a menu entry per architecture that shares a network. +my %GRUB2_DISCOVERY_ARCHES = ( + riscv64 => 'riscv64', +); + sub handled_commands { return { mknb => 'mknb', @@ -703,6 +711,9 @@ sub process_request { } } elsif ($arch =~ /ppc/) { mkpath("$tftpdir/pxelinux.cfg/p/"); + } elsif (exists $GRUB2_DISCOVERY_ARCHES{$arch}) { + mkpath("$tftpdir/boot/grub2"); + chmod(0755, "$tftpdir/boot/grub2"); } my $dopxe = 0; foreach (keys %{$normnets}) { @@ -816,11 +827,97 @@ sub process_request { print $cfgfile " initrd http://" . $xcatd_address . "$portsuffix/$initrd_file\n"; print $cfgfile ' append "xcatd=' . $xcatd_address . ":$xcatdport $consolecmdline\"\n"; close($cfgfile); + } elsif (exists $GRUB2_DISCOVERY_ARCHES{$arch}) { + # Unlike the PXELINUX files, which only look at the legacy address of the network, + # the discovery configuration is also dropped when the xcatd address chosen for the + # network sits on a :noboot interface: it would send Genesis to an interface that + # must not serve booting. + if (defined($nobootnicips{ $hexnets->{$_} }) or defined($nobootnicips{$xcatd_address})) { + unlink("$tftpdir/boot/grub2/grub.cfg-" . uc($_)); + next; + } + _write_grub2_discovery_config( + tftpdir => $tftpdir, + hexnet => $_, + xcatd_address => $xcatd_address, + xcatdport => $xcatdport, + consolecmdline => $consolecmdline, + ); } } + if (exists $GRUB2_DISCOVERY_ARCHES{$arch} and !-e "$tftpdir/boot/grub2/grub2.$arch") { + # The discovery configurations are only reached through this boot loader, which + # xCAT does not build: say so instead of letting the nodes time out in firmware. + $callback->({ data => ["Note: $tftpdir/boot/grub2/grub2.$arch is missing; $arch nodes need it to reach these configurations (it is installed by grub2-xcat, or copied from the EL $arch installation media)"] }); + } if ($configfileonly) { $callback->({ data => ["Write netboot config file done"] }); } } +# Return the grub2-class architectures whose Genesis kernel and initrd are +# published under $tftpdir/xcat, as [arch, grub_cpu, kernel, initrd] with the +# file names relative to the TFTP root. + +sub _grub2_discovery_arches { + my ($tftpdir) = @_; + my @present; + foreach my $arch (sort keys %GRUB2_DISCOVERY_ARCHES) { + next unless -e "$tftpdir/xcat/genesis.kernel.$arch"; + my ($initrd) = grep { -e "$tftpdir/$_" } + map { "xcat/genesis.fs.$arch.$_" } qw(lzma gz); + next unless $initrd; + push @present, + [ $arch, $GRUB2_DISCOVERY_ARCHES{$arch}, "xcat/genesis.kernel.$arch", $initrd ]; + } + return @present; +} + +# Write the grub2 discovery configuration for one network. +# +# grub2., net booted from $tftpdir/boot/grub2, looks for grub.cfg-01-, +# then grub.cfg-<8 hex digit ip>, then ever shorter prefixes of that ip, and +# finally grub.cfg. nodeset writes the per-node files (full ip and mac), so a +# per-network prefix is only reached by clients without a node configuration: +# discovery. The file is rebuilt from the Genesis artifacts present under +# $tftpdir/xcat and removed when none are left. Returns the path written. +sub _write_grub2_discovery_config { + my (%args) = @_; + my $tftpdir = $args{tftpdir}; + my $hexnet = uc($args{hexnet}); + my $cfgpath = "$tftpdir/boot/grub2/grub.cfg-$hexnet"; + my @arches = _grub2_discovery_arches($tftpdir); + unless (@arches) { + unlink($cfgpath); + return; + } + my $cmdline = "xcatd=$args{xcatd_address}:$args{xcatdport}"; + if (defined($args{consolecmdline}) and $args{consolecmdline} ne '') { + $cmdline .= " $args{consolecmdline}"; + } + my $content = "# xCAT Genesis discovery for network $hexnet - generated by mknb, do not edit\n"; + $content .= "set default=0\n"; + $content .= "set timeout=5\n"; + my $keyword = 'if'; + foreach my $entry (@arches) { + my ($arch, $grub_cpu, $kernel, $initrd) = @{$entry}; + $content .= "$keyword [ \"\$grub_cpu\" = \"$grub_cpu\" ]; then\n"; + $content .= "menuentry \"xCAT Genesis $arch\" {\n"; + $content .= " linux /$kernel $cmdline BOOTIF=\$net_default_mac\n"; + $content .= " initrd /$initrd\n"; + $content .= "}\n"; + $keyword = 'elif'; + } + $content .= "fi\n"; + # nodeset hard-links grub.cfg-<8 hex digit ip> to the node file; on a /32 network that is + # this file's name, so replace the name instead of truncating a shared inode. + unlink($cfgpath); + my $cfg; + open($cfg, ">", $cfgpath) or return; + print $cfg $content; + close($cfg); + chmod(0644, $cfgpath); + return $cfgpath; +} + 1;