2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-05-05 16:49:08 +00:00

fix: improve Ubuntu LTS provisioning support

This commit is contained in:
Vinícius Ferrão
2026-04-28 16:41:50 -03:00
parent d7748b6e3a
commit 1babd7b0e4
40 changed files with 1383 additions and 420 deletions

View File

@@ -351,6 +351,62 @@ backend scope described above.
``ALLOC_FAIL_NO_POOLS``; DHCP/TFTP/GRUB handoff passed and the node
reached Genesis with the temporary EL9 ppc64le payload workaround.
Ubuntu LTS KVM Validation Snapshot
----------------------------------
As of April 29, 2026, the Ubuntu LTS KVM validation for the Ubuntu
provisioning restoration work has the following result:
.. list-table::
:header-rows: 1
:widths: 14 10 10 12 12 42
* - Platform
- Arch
- Backend
- BIOS
- UEFI
- Notes
* - Ubuntu 18.04 LTS
- ``x86_64``
- ``ISC``
- Pass
- Pass
- Stateless and stateful compute boots passed against an Ubuntu 18.04
headnode. The 18.04 debian-installer initrd did not include
``virtio_blk`` in this KVM environment, so stateful VMs used
``virtio-scsi`` disks. The legacy preseed disables installer
update/security services so stateful installs and ``apt-get update``
use only the local xCAT install media. Persistent interface naming used
``R::net.ifnames=0 R::biosdevname=0``.
* - Ubuntu 20.04 LTS
- ``x86_64``
- ``ISC``
- Pass
- Pass
- Stateless and stateful compute boots passed. Stateful validation used a
manual static reservation workaround for the known forced-ISC OMAPI
issue.
* - Ubuntu 22.04 LTS
- ``x86_64``
- ``Kea``
- Pass
- Pass
- Stateless and stateful compute boots passed. Kea 2.0.2 configuration
validation with ``kea-dhcp4 -t`` passed on the Ubuntu 22.04 headnode.
``makedns -n`` starts ``bind9`` successfully, and the DHCP section of
``xcatprobe xcatmn`` passed on consecutive runs.
* - Ubuntu 24.04 LTS
- ``x86_64``
- ``Kea``
- Pass
- Pass
- Stateless and stateful compute boots passed. Kea 2.4.1 configuration
validation with ``kea-dhcp4 -t`` passed on the Ubuntu 24.04 headnode.
``makedns -n`` starts ``bind9`` successfully, and the DHCP section of
``xcatprobe xcatmn`` passed on consecutive runs. UEFI validation used
OVMF Secure Boot disabled.
Skipped Rows
------------

View File

@@ -290,7 +290,7 @@ Unit tests:
* host reservation formatting
* subnet and pool mapping
* Kea reservation policy flags for subnet reservations and out-of-pool-only
overrides
overrides, plus DHCPv4 ``match-client-id`` behavior
* backend selection and override behavior
Configuration validation tests:
@@ -319,6 +319,8 @@ Integration matrix:
* EL9 plus ISC
* EL9 plus forced Kea
* EL10 plus Kea
* Ubuntu 18.04 plus ISC
* Ubuntu 20.04 plus ISC
* Ubuntu 22.04 plus Kea
* Ubuntu 24.04 plus Kea
@@ -353,7 +355,8 @@ ordinary containers.
Open test infrastructure details to confirm:
* SSH access method and user for ``rome01.local.versatushpc.com.br``
* available base images for EL9, EL10, Ubuntu 22.04, and Ubuntu 24.04
* available base images for EL9, EL10, Ubuntu 18.04, Ubuntu 20.04, Ubuntu
22.04, and Ubuntu 24.04
* libvirt network names and whether isolated DHCP test networks are already
available
* whether nested or privileged test guests can run DHCP client and PXE tests

View File

@@ -63,10 +63,11 @@ sub render_dhcp4_config {
type => 'memfile',
name => '/var/lib/kea/kea-leases4.csv',
},
'valid-lifetime' => _integer( _first_defined( $intent->{'valid-lifetime'}, $intent->{valid_lifetime}, 43200 ) ),
'reservations-in-subnet' => _json_bool( _first_defined( $intent->{'reservations-in-subnet'}, $intent->{reservations_in_subnet}, 1 ) ),
'reservations-out-of-pool' => _json_bool( _first_defined( $intent->{'reservations-out-of-pool'}, $intent->{reservations_out_of_pool}, 1 ) ),
subnet4 => [ map { $self->_render_subnet4($_) } @{ _first_defined( $intent->{subnet4}, $intent->{subnets}, [] ) } ],
'valid-lifetime' => _integer( _first_defined( $intent->{'valid-lifetime'}, $intent->{valid_lifetime}, 43200 ) ),
'reservations-in-subnet' => _json_bool( _first_defined( $intent->{'reservations-in-subnet'}, $intent->{reservations_in_subnet}, 1 ) ),
'reservations-out-of-pool' => _json_bool( _first_defined( $intent->{'reservations-out-of-pool'}, $intent->{reservations_out_of_pool}, 1 ) ),
'match-client-id' => _json_bool( _first_defined( $intent->{'match-client-id'}, $intent->{match_client_id}, 0 ) ),
subnet4 => [ map { $self->_render_subnet4($_) } @{ _first_defined( $intent->{subnet4}, $intent->{subnets}, [] ) } ],
);
$dhcp4{'control-socket'} = $intent->{'control-socket'} if $intent->{'control-socket'};

View File

@@ -158,6 +158,66 @@ sub get_os {
return $os;
}
sub _netplan_get {
my $key = shift;
open(my $fh, '-|', 'netplan', 'get', $key) or return undef;
my $val = <$fh>;
close $fh;
return undef if $?;
chomp $val if defined $val;
return $val;
}
sub _command_available {
my $cmd = shift;
for my $dir (split /:/, $ENV{PATH} || '') {
next unless $dir;
return 1 if -x "$dir/$cmd";
}
return 0;
}
sub _netplan_has_static_ip {
my ($nic, $ip) = @_;
return 0 unless _command_available('netplan');
(my $escaped_nic = $nic) =~ s/\./\\./g;
for my $devtype (qw(ethernets bonds bridges vlans)) {
my $dev_obj = _netplan_get("$devtype.$escaped_nic");
next unless defined $dev_obj;
next if ($dev_obj eq 'null' || $dev_obj eq '');
my $addresses = _netplan_get("$devtype.$escaped_nic.addresses");
next unless defined $addresses;
next if ($addresses eq 'null' || $addresses eq '');
next unless $addresses =~ /(?:^|[\s\[,])\Q$ip\E(?:\/\d+)?(?:$|[\s\],])/m;
my $dhcp_val = _netplan_get("$devtype.$escaped_nic.dhcp4");
return 0 if defined $dhcp_val && $dhcp_val =~ /true/i;
return 1;
}
return 0;
}
sub dhcp_query_reply_mac {
my ($reply, $node, $ip) = @_;
return unless defined $reply && defined $node && defined $ip;
return $1 if $reply =~ /\Q$node\E:\s+ip-address\s*=\s*\Q$ip\E,?\s+hardware-address\s*=\s*([0-9a-f:]+)/i;
return;
}
sub dhcp_query_reply_matches {
my ($reply, $node, $ip, $mac) = @_;
return 0 unless defined $mac;
my $reply_mac = dhcp_query_reply_mac($reply, $node, $ip);
return 0 unless defined $reply_mac;
return lc($reply_mac) eq lc($mac);
}
#------------------------------------------
=head3
@@ -206,8 +266,9 @@ sub is_static_ip {
my $output2 = `cat /etc/sysconfig/network/ifcfg-$nic 2>&1 |grep -i BOOTPROTO`;
$rst = 1 if (($output1 =~ /$ip/) && ($output2 =~ /static/i));
} elsif ($os =~ /ubuntu/) {
my $output = `cat /etc/network/interfaces 2>&1|grep -E "iface\\s+$nic"`;
$rst = 1 if ($output =~ /static/i);
return 1 if _netplan_has_static_ip($nic, $ip);
my $output = `cat /etc/network/interfaces 2>&1`;
$rst = 1 if (($output =~ /iface\s+\Q$nic\E\s+inet\s+static/i) && ($output =~ /\baddress\s+\Q$ip\E(?:\/\d+)?\b/i));
}
return $rst;
}

View File

@@ -1093,10 +1093,9 @@ sub check_dhcp_service {
my $snip = xCAT::NetworkUtils->getipaddr($sntmp);
my $snmac = `lsdef $sntmp -i mac -c 2>&1| awk -F'=' '{print \$2}'`;
chomp($snmac);
my $tmpmac;
if ($tmp =~ /$sntmp: ip-address = $snip, hardware-address = (.+)/) {
$tmpmac = $1;
if ($tmpmac !~ $snmac) {
my $tmpmac = probe_utils::dhcp_query_reply_mac($tmp, $sntmp, $snip);
if (defined($tmpmac)) {
if (lc($tmpmac) ne lc($snmac)) {
push @$error_ref, "DHCP server reply is wrong";
returncmdoutput($tmp, $error_ref) if ($verbose);
$rc = 1;
@@ -1108,6 +1107,10 @@ sub check_dhcp_service {
}
} else {
# Clean up any leftover test node from a previous failed run
`makedhcp -d xcatmntest 2>&1`;
`rmdef xcatmntest 2>&1`;
my $tmp = `chdef xcatmntest groups=all ip=$serverip mac=aa:aa:aa:aa:aa:aa 2>&1`;
if ($?) {
push @$error_ref, "Node simulation by 'chdef' has failed";
@@ -1121,7 +1124,7 @@ sub check_dhcp_service {
`cp /etc/hosts /etc/hosts.bak.probe > /dev/null 2>&1`;
open HOSTFILE, ">> /etc/hosts";
print HOSTFILE "$serverip xcatmntest xcatmntest.$domain";
print HOSTFILE "$serverip xcatmntest xcatmntest.$domain\n";
close HOSTFILE;
probe_utils->send_msg($outputtarget, "d", "To do 'makedhcp xcatmntest'") if ($verbose);
@@ -1130,40 +1133,45 @@ sub check_dhcp_service {
push @$error_ref, "makedhcp xcatmntest failed";
returncmdoutput($tmp, $error_ref) if ($verbose);
$rc = 1;
`makedhcp -d xcatmntest 2>&1`;
`rmdef xcatmntest 2>&1`;
if (-f "/etc/hosts.bak.probe") {
unlink "/etc/hosts";
move("/etc/hosts.bak.probe", "/etc/hosts");
}
last;
}
# Allow DHCP server time to commit the OMAPI-added entry
sleep 1;
$tmp = `makedhcp -q xcatmntest 2>&1`;
if ($?) {
push @$error_ref, "makedhcp -q xcatmntest failed";
returncmdoutput($tmp, $error_ref) if ($verbose);
$rc = 1;
`makedhcp -d xcatmntest 2>&1 && rmdef xcatmntest 2>&1`;
last;
}
chomp($tmp);
if (length($tmp) == 0) {
# makedhcp -q did not return anything, and RC was 0
chomp($tmp) if defined($tmp);
if (!$rc && length($tmp) == 0) {
push @$error_ref, "DHCP server returned no data for 'makedhcp -q xcatmntest'";
returncmdoutput($tmp, $error_ref) if ($verbose);
$rc = 1;
last;
}
if ($tmp !~ /xcatmntest: ip-address = $serverip, hardware-address = aa:aa:aa:aa:aa:aa/) {
if (!$rc && !probe_utils::dhcp_query_reply_matches($tmp, 'xcatmntest', $serverip, 'aa:aa:aa:aa:aa:aa')) {
push @$error_ref, "DHCP server reply does not match expected IP and MAC";
returncmdoutput($tmp, $error_ref) if ($verbose);
$rc = 1;
`makedhcp -d xcatmntest 2>&1 && rmdef xcatmntest 2>&1`;
last;
}
# Always clean up the test node regardless of success or failure
push @$error_ref, "Start clearing simulation information for dhcp test" if ($verbose);
$tmp = `makedhcp -d xcatmntest 2>&1 && rmdef xcatmntest 2>&1`;
$tmp = `makedhcp -d xcatmntest 2>&1 && rmdef xcatmntest 2>&1`;
returncmdoutput($tmp, $error_ref) if ($verbose);
unlink "/etc/hosts";
move("/etc/hosts.bak.probe", "/etc/hosts");
if (-f "/etc/hosts.bak.probe") {
unlink "/etc/hosts";
move("/etc/hosts.bak.probe", "/etc/hosts");
}
}
}
if ($rc) {
@@ -1492,5 +1500,3 @@ exit $rst;

View File

@@ -1590,28 +1590,38 @@ sub setupNFSTree {
shift @entries;
if (grep /\Q$nfsdirectory\E/, @entries) {
$callback->({ data => ["$nfsdirectory has been exported already!"] });
# nothing to do
} else {
$cmd = "/usr/sbin/exportfs :$nfsdirectory";
$cmd = "/usr/sbin/exportfs :$nfsdirectory -o rw,no_root_squash,sync,no_subtree_check,insecure";
xCAT::Utils->runcmd($cmd, 0);
# exportfs can export this directory immediately
$callback->({ data => ["now $nfsdirectory is exported!"] });
$cmd = "cat /etc/exports";
@entries = xCAT::Utils->runcmd($cmd, 0);
unless (my $entry = grep /\Q$nfsdirectory\E/, @entries) {
}
#if there's no entry in /etc/exports, one with default options will be added
$cmd = qq{echo "$nfsdirectory *(rw,no_root_squash,sync,no_subtree_check)" >> /etc/exports};
xCAT::Utils->runcmd($cmd, 0);
$callback->({ data => ["$nfsdirectory is added to /etc/exports with default option"] });
}
if (ensure_nfs_export_option($nfsdirectory, 'insecure')) {
xCAT::Utils->runcmd("/usr/sbin/exportfs -r", 0);
$callback->({ data => ["added insecure to existing $nfsdirectory export"] });
} elsif (!nfs_export_exists($nfsdirectory)) {
$cmd = qq{echo "$nfsdirectory *(rw,no_root_squash,sync,no_subtree_check,insecure)" >> /etc/exports};
xCAT::Utils->runcmd($cmd, 0);
$callback->({ data => ["$nfsdirectory is added to /etc/exports with default option"] });
}
}
}
}
sub nfs_export_exists {
my ( $dir, %opts ) = _nfs_method_args(@_);
my @files = _nfs_export_files(%opts);
for my $file (@files) {
open(my $fh, '<', $file) or next;
while (<$fh>) {
next if /^\s*#/;
return 1 if /^\s*\Q$dir\E\s/;
}
close $fh;
}
return 0;
}
sub setupStatemnt {
my $sip = shift;
if (($sip) && ($sip =~ /xCAT::SvrUtils/))
@@ -1639,26 +1649,120 @@ sub setupStatemnt {
if (grep /\Q$nfsdirectory\E/, @entries) {
$callback->({ data => ["$nfsdirectory has been exported already!"] });
} else {
$cmd = "/usr/sbin/exportfs :$nfsdirectory -o rw,no_root_squash,sync,no_subtree_check";
$cmd = "/usr/sbin/exportfs :$nfsdirectory -o rw,no_root_squash,sync,no_subtree_check,insecure";
xCAT::Utils->runcmd($cmd, 0);
$callback->({ data => ["now $nfsdirectory is exported!"] });
}
# add the directory into /etc/exports if not exist
$cmd = "cat /etc/exports";
@entries = xCAT::Utils->runcmd($cmd, 0);
if (my $entry = grep /\Q$nfsdirectory\E/, @entries) {
unless ($entry =~ m/rw/) {
$callback->({ data => ["The $nfsdirectory should be with rw option in /etc/exports"] });
}
} else {
xCAT::Utils->runcmd(qq{echo "$nfsdirectory *(rw,no_root_squash,sync,no_subtree_check)" >>/etc/exports}, 0);
$callback->({ data => ["$nfsdirectory is added into /etc/exports with default options"] });
}
if (ensure_nfs_export_option($nfsdirectory, 'insecure')) {
xCAT::Utils->runcmd("/usr/sbin/exportfs -r", 0);
$callback->({ data => ["added insecure to existing $nfsdirectory export"] });
} elsif (!nfs_export_exists($nfsdirectory)) {
xCAT::Utils->runcmd(qq{echo "$nfsdirectory *(rw,no_root_squash,sync,no_subtree_check,insecure)" >>/etc/exports}, 0);
$callback->({ data => ["$nfsdirectory is added into /etc/exports with default options"] });
}
}
}
sub ensure_nfs_export_option {
my ( $dir, @args ) = _nfs_method_args(@_);
local @_ = @args;
my $option = shift || 'insecure';
my %opts = @_;
my @files = _nfs_export_files(%opts);
my $changed = 0;
for my $file (@files) {
open(my $fh, '<', $file) or next;
my @raw = <$fh>;
close $fh;
my @logical;
my $buf = '';
for my $r (@raw) {
chomp $r;
if ($r =~ s/\s*\\$//) {
$buf .= "$r ";
next;
}
$buf .= $r;
push @logical, $buf;
$buf = '';
}
push @logical, $buf if $buf ne '';
my $file_changed = 0;
for my $line (@logical) {
next if $line =~ /^\s*#/;
next unless $line =~ /^\s*\Q$dir\E\s/;
my $line_changed = 0;
my $comment = '';
$comment = " $1" if $line =~ s/\s+(#.*)$//;
my $tail = $line;
$tail =~ s/^\s*\Q$dir\E\s+//;
my @tokens;
while ($tail =~ /(\S+(?:\([^)]*\))?)/g) {
push @tokens, $1;
}
my @out;
for my $tok (@tokens) {
if ($tok =~ /^-(.+)/) {
my $opts = $1;
unless (_option_list_has($opts, $option)) {
$tok = "-$opts,$option";
$line_changed = 1;
}
} elsif ($tok =~ /^([^(]+)\(([^)]*)\)$/) {
my ($client, $opts) = ($1, $2);
unless (_option_list_has($opts, $option)) {
$tok = "$client($opts,$option)";
$line_changed = 1;
}
} else {
$tok = "$tok($option)";
$line_changed = 1;
}
push @out, $tok;
}
if ($line_changed) {
$line = "$dir " . join(" ", @out) . "$comment";
$file_changed = 1;
}
}
if ($file_changed) {
open(my $wfh, '>', $file) or next;
print $wfh map { "$_\n" } @logical;
close $wfh;
$changed = 1;
}
}
return $changed;
}
sub _nfs_method_args {
my @args = @_;
shift @args if @args && (ref($args[0]) || $args[0] eq __PACKAGE__);
return @args;
}
sub _nfs_export_files {
my %opts = @_;
return @{ $opts{files} } if ref($opts{files}) eq 'ARRAY';
my @files = ('/etc/exports');
push @files, glob('/etc/exports.d/*.exports') if -d '/etc/exports.d';
return @files;
}
sub _option_list_has {
my ($list, $option) = @_;
foreach my $item (split /,/, $list) {
$item =~ s/^\s+|\s+$//g;
return 1 if $item eq $option;
}
return 0;
}
#-------------------------------------------------------------------------------------------
# Common method to send info back to the client
# The last two args are optional, though $allerrornodes will unlikely be there without $node

View File

@@ -1132,17 +1132,23 @@ sub mirrorspec {
if (!$pkgdir) {
$pkgdir = $_;
} else {
my $osuurl = "http://" . $masternode.':'.$ENV{httpport} . $_ . " ./";
my $httpport = $ENV{HTTPPORT} || $ENV{httpport} || '80';
my $osuurl = "http://" . $masternode . ':' . $httpport . $_ . " ./";
push @mirrors, $osuurl;
}
}
}
if ($pkgdir) {
my $httpport = $ENV{HTTPPORT} || $ENV{httpport} || '80';
my $security_host = $masternode;
$security_host .= ':' . $httpport if $httpport ne '80';
$line .= "
d-i mirror/country string manual\n
d-i mirror/protocol string http\n
d-i mirror/http/directory string $pkgdir\n
d-i apt-setup/security_host string $security_host\n
d-i apt-setup/security_path string $pkgdir\n
d-i mirror/http/proxy string\n";
}

View File

@@ -12,6 +12,7 @@ use strict;
use xCAT::Table;
use xCAT::Utils;
use xCAT::SvrUtils;
use xCAT::TableUtils;
use xCAT::NetworkUtils;
use xCAT::DHCP::Backend;
@@ -398,15 +399,11 @@ sub setupInstallloc
# add /install to /etc/exports - if needed
#
my $cmd = "/bin/cat /etc/exports | grep '$installdir'";
my $outref = xCAT::Utils->runcmd("$cmd", -1);
my $changed_exports;
if ($::RUNCMD_RC != 0)
if (!xCAT::SvrUtils->nfs_export_exists($installdir))
{
# ok - then add this entry
my $cmd =
"/bin/echo '$installdir *(rw,no_root_squash,sync,no_subtree_check)' >> /etc/exports";
"/bin/echo '$installdir *(rw,no_root_squash,sync,no_subtree_check,insecure)' >> /etc/exports";
my $outref = xCAT::Utils->runcmd("$cmd", 0);
if ($::RUNCMD_RC != 0)
{
@@ -418,11 +415,16 @@ sub setupInstallloc
$changed_exports++;
}
}
else
{
if (xCAT::SvrUtils->ensure_nfs_export_option($installdir, 'insecure'))
{
$changed_exports++;
}
}
if ($changed_exports)
{
# restart nfs
&setup_NFS($nodename);
my $cmd = "/usr/sbin/exportfs -a";
@@ -431,7 +433,6 @@ sub setupInstallloc
{
xCAT::MsgUtils->message('S', "Error with $cmd.");
}
}
}
}

View File

@@ -27,18 +27,34 @@ my $ddns_key_path = "/etc/xcat/ddns.key";
# Net::DNS >= 1.36 removed support for sign_tsig($keyname, $secret) and now
# expects a keyfile. Keep the keyfile in sync with the xcat_key secret.
sub ddns_tsig_algorithm {
my ($ctx) = @_;
# Older Net::DNS releases used by Ubuntu 22.04 cannot sign updates with the
# keyfile API, so keep the generated xCAT key on the legacy algorithm that
# both old and new Net::DNS can use.
return "hmac-md5" if (Net::DNS->VERSION < 1.36);
return $ctx->{tsig_algorithm} || "hmac-md5";
}
sub ddns_key_contents {
my ($ctx) = @_;
my $algorithm = ddns_tsig_algorithm($ctx);
return
"key \"xcat_key\" {\n"
. "\talgorithm $algorithm;\n"
. "\tsecret \"" . $ctx->{privkey} . "\";\n"
. "};\n\n";
}
sub ensure_ddns_key_file {
my ($ctx) = @_;
return unless (Net::DNS->VERSION >= 1.36);
return unless ($ctx && $ctx->{privkey});
my $algorithm = $ctx->{tsig_algorithm} || "hmac-sha256";
my $contents =
"key \"xcat_key\" {\n"
. "\talgorithm $algorithm;\n"
. "\tsecret \"" . $ctx->{privkey} . "\";\n"
. "};\n\n";
my $contents = ddns_key_contents($ctx);
if (open(my $existing_fh, "<", $ddns_key_path)) {
local $/;
@@ -1278,18 +1294,22 @@ sub update_namedconf {
$gotkey = 1;
my $algorithmnow;
if ($ctx->{privkey}) {
#for now, assume the field is correct
#push @newnamed,"key xcat_key {\n","\talgorithm hmac-md5;\n","\tsecret \"".$ctx->{privkey}."\";\n","};\n\n";
push @newnamed, $line;
my @keyblock = ($line);
do {
$i++;
$line = $currnamed[$i];
if ($line =~ /^\s*algorithm\s+([^;\s]+)\s*;/) {
$algorithmnow = $1;
}
push @newnamed, $line;
push @keyblock, $line;
} while ($line !~ /^\};/);
if ($algorithmnow && Net::DNS->VERSION < 1.36 && lc($algorithmnow) ne "hmac-md5") {
$ctx->{tsig_algorithm} = "hmac-md5";
push @newnamed, ddns_key_contents($ctx);
$ctx->{restartneeded} = 1;
} else {
push @newnamed, @keyblock;
}
} else {
push @newnamed, $line;
while ($line !~ /^\};/) { #skip the old file zone
@@ -1406,8 +1426,8 @@ sub update_namedconf {
$ctx->{privkey} = encode_base64(genpassword(32));
chomp($ctx->{privkey});
}
$ctx->{tsig_algorithm} ||= "hmac-sha256";
my $contents = "key \"xcat_key\" {\n" . "\talgorithm " . $ctx->{tsig_algorithm} . ";\n" . "\tsecret \"" . $ctx->{privkey} . "\";\n" . "};\n\n";
$ctx->{tsig_algorithm} ||= ddns_tsig_algorithm($ctx);
my $contents = ddns_key_contents($ctx);
push @newnamed, $contents;
ensure_ddns_key_file($ctx);
$ctx->{restartneeded} = 1;

View File

@@ -418,8 +418,27 @@ sub copycd
my @line = split(" ", `ls -lh $installroot/$distname/$arch/dists/ | grep dr`);
my $dist = $line[ @line - 1 ];
# touches the Packages file so that deb packaging works
system("touch $installroot/$distname/$arch/dists/$dist/restricted/binary-$debarch/Packages");
# Ensure the uncompressed Packages file exists. The ISO ships only
# Packages.gz; an empty Packages file causes apt hash-sum failures
# during autoinstall because apt reads the zero-length file instead
# of falling back to the compressed variant.
my $pkgdir = "$installroot/$distname/$arch/dists/$dist/restricted/binary-$debarch";
if (-d $pkgdir) {
my $pkgfile = "$pkgdir/Packages";
my $pkggz = "$pkgfile.gz";
if (-f $pkggz && (! -f $pkgfile || -z $pkgfile)) {
if (open(my $in, '-|', 'gzip', '-dc', $pkggz)) {
if (open(my $out, '>', $pkgfile)) {
copy($in, $out);
close($out);
}
close($in);
}
} elsif (! -f $pkgfile) {
open(my $fh, '>', $pkgfile);
close($fh) if $fh;
}
}
# removes the links unstable and testing, otherwise the repository does not work for debian
system("rm -f $installroot/$distname/$arch/dists/unstable");
@@ -753,9 +772,12 @@ sub mkinstall {
}
mkpath($autoinstfile);
# create empty meta-data file
# create empty meta-data and vendor-data files
open(my $fh, ">", $autoinstfile . "/meta-data");
close($fh);
open($fh, ">", $autoinstfile . "/vendor-data");
print $fh "{}\n";
close($fh);
# point the template output at the /user-data file
$autoinstfile = "$autoinstfile/user-data";
@@ -937,6 +959,7 @@ sub mkinstall {
if (using_subiquity($os,$tmplfile)) {
$kcmdline .= " autoinstall ip=dhcp netboot=nfs nfsroot=${instserver}:${pkgdir}";
$kcmdline .= " ds=nocloud-net;s=http://${instserver}:${httpport}/install/autoinst/${node}/";
$kcmdline .= " ---";
} else {
$kcmdline .= " url=http://${instserver}:$httpport/install/autoinst/$node";
$kcmdline .= " mirror/http/hostname=${instserver}:$httpport";
@@ -1766,20 +1789,18 @@ sub setupNFSTree {
if (grep /\Q$nfsdirectory\E/, @entries) {
$callback->({ data => ["$nfsdirectory has been exported already!"] });
} else {
$cmd = "/usr/sbin/exportfs :$nfsdirectory";
$cmd = "/usr/sbin/exportfs :$nfsdirectory -o rw,no_root_squash,sync,no_subtree_check,insecure";
xCAT::Utils->runcmd($cmd, 0);
# exportfs can export this directory immediately
$callback->({ data => ["now $nfsdirectory is exported!"] });
$cmd = "cat /etc/exports";
@entries = xCAT::Utils->runcmd($cmd, 0);
unless (my $entry = grep /\Q$nfsdirectory\E/, @entries) {
}
# if no entry in /etc/exports, one entry with default options will be added
$cmd = qq{echo "$nfsdirectory *(rw,no_root_squash,sync,no_subtree_check)" >> /etc/exports};
xCAT::Utils->runcmd($cmd, 0);
$callback->({ data => ["$nfsdirectory is added to /etc/exports with default option"] });
}
if (xCAT::SvrUtils->ensure_nfs_export_option($nfsdirectory, 'insecure')) {
xCAT::Utils->runcmd("/usr/sbin/exportfs -r", 0);
$callback->({ data => ["added insecure to existing $nfsdirectory export"] });
} elsif (!xCAT::SvrUtils->nfs_export_exists($nfsdirectory)) {
$cmd = qq{echo "$nfsdirectory *(rw,no_root_squash,sync,no_subtree_check,insecure)" >> /etc/exports};
xCAT::Utils->runcmd($cmd, 0);
$callback->({ data => ["$nfsdirectory is added to /etc/exports with default option"] });
}
}
}
@@ -1808,21 +1829,17 @@ sub setupStatemnt {
if (grep /\Q$nfsdirectory\E/, @entries) {
$callback->({ data => ["$nfsdirectory has been exported already!"] });
} else {
$cmd = "/usr/sbin/exportfs :$nfsdirectory -o rw,no_root_squash,sync,no_subtree_check";
$cmd = "/usr/sbin/exportfs :$nfsdirectory -o rw,no_root_squash,sync,no_subtree_check,insecure";
xCAT::Utils->runcmd($cmd, 0);
$callback->({ data => ["now $nfsdirectory is exported!"] });
}
# add the directory into /etc/exports if not exist
$cmd = "cat /etc/exports";
@entries = xCAT::Utils->runcmd($cmd, 0);
if (my $entry = grep /\Q$nfsdirectory\E/, @entries) {
unless ($entry =~ m/rw/) {
$callback->({ data => ["The $nfsdirectory should be with rw option in /etc/exports"] });
}
} else {
xCAT::Utils->runcmd(qq{echo "$nfsdirectory *(rw,no_root_squash,sync,no_subtree_check)" >> /etc/exports}, 0);
$callback->({ data => ["$nfsdirectory is added into /etc/exports with default options"] });
}
if (xCAT::SvrUtils->ensure_nfs_export_option($nfsdirectory, 'insecure')) {
xCAT::Utils->runcmd("/usr/sbin/exportfs -r", 0);
$callback->({ data => ["added insecure to existing $nfsdirectory export"] });
} elsif (!xCAT::SvrUtils->nfs_export_exists($nfsdirectory)) {
xCAT::Utils->runcmd(qq{echo "$nfsdirectory *(rw,no_root_squash,sync,no_subtree_check,insecure)" >> /etc/exports}, 0);
$callback->({ data => ["$nfsdirectory is added into /etc/exports with default options"] });
}
}
}

View File

@@ -9,6 +9,10 @@ use lib "$::XCATROOT/lib/perl";
use strict;
use IPC::Open2;
use IPC::Open3;
use IO::Select;
use Symbol qw/gensym/;
use POSIX qw/WNOHANG/;
use xCAT::Table;
#use Data::Dumper;
@@ -149,6 +153,97 @@ sub handled_commands
return { makedhcp => "dhcp", };
}
######################################################
# Run omshell to query a host and parse the output.
# Uses a heredoc pipe instead of open2 to avoid deadlock
# when omshell produces output before all input is consumed.
######################################################
sub _omshell_query_host
{
my $node = shift;
my $omapiuser = shift;
my $omapikey = shift;
my $port = shift;
my $port_cmd = defined($port) ? "port $port\n" : "";
my $omcmds = "${port_cmd}key $omapiuser \"$omapikey\"\nconnect\nnew host\nset name = \"$node\"\nopen\nclose\n";
my @output = _run_omshell($omcmds);
return _parse_omshell_host_output($node, @output);
}
sub _run_omshell
{
my $omcmds = shift;
my ($in, $out);
my $err = gensym;
my $pid = eval { open3($in, $out, $err, '/usr/bin/omshell') };
return () if $@ || !$pid;
print $in $omcmds;
close($in);
my @output;
my $selector = IO::Select->new($out, $err);
my $deadline = time + 10;
while ($selector->count) {
my $remaining = $deadline - time;
if ($remaining <= 0) {
kill 'TERM', $pid;
last;
}
foreach my $fh ($selector->can_read($remaining)) {
my $line = <$fh>;
if (defined $line) {
push @output, $line if fileno($fh) == fileno($out);
} else {
$selector->remove($fh);
close($fh);
}
}
}
for (1 .. 10) {
last if waitpid($pid, WNOHANG) == $pid;
select(undef, undef, undef, 0.1);
}
if (waitpid($pid, WNOHANG) == 0) {
kill 'KILL', $pid;
waitpid($pid, 0);
}
return @output;
}
sub _parse_omshell_host_output
{
my ($node, @output) = @_;
my ($nname, $ipaddr, $hwaddr);
foreach my $line (@output) {
chomp $line;
if ($line =~ /^\s*name\s*=\s*"?(.*?)"?\s*$/) {
$nname = $1 if $1 eq $node;
} elsif ($line =~ /^\s*hardware-address\s*=/) {
$hwaddr = $line;
} elsif ($line =~ /^\s*ip-address\s*=\s*(.+?)\s*$/) {
my $ip = $1;
if ($ip =~ /^(?:[[:xdigit:]]{1,2}:){3}[[:xdigit:]]{1,2}$/) {
my @parts = split(/\:/, $ip);
$ipaddr = "ip-address = " . join(".", map { hex($_) } @parts);
} else {
$ipaddr = "ip-address = $ip";
}
}
}
$nname = $node if !$nname && $ipaddr;
return ($nname, $ipaddr, $hwaddr);
}
######################################################
# List nodes in DHCP for both IPv4 and IPv6
######################################################
@@ -156,154 +251,41 @@ sub listnode
{
my $node = shift;
my $callback = shift;
my $lines;
my $ipaddr = "";
my $hwaddr;
my $nname;
my $rsp;
my ($OMOUT, $OMIN, $OMOUT6, $OMIN6);
my $usingipv6;
my $omapiuser;
my $omapikey;
# Collect the omapi user and key from the passwd table
my $pwtab = xCAT::Table->new("passwd");
my @pws = $pwtab->getAllAttribs('key', 'username', 'password', 'cryptmethod', 'authdomain', 'comments', 'disable');
foreach (@pws) {
# Look for the opapi entry in the passwd table
if ($_->{key} =~ "omapi") { #omapi key
# save username and password for omapi connection
if ($_->{key} =~ "omapi") {
$omapiuser = $_->{username};
$omapikey = $_->{password};
}
}
# Look through the networks table for networks with IPv6 format for address
my $usingipv6;
my $nettab = xCAT::Table->new("networks");
my @vnets = $nettab->getAllAttribs('net', 'mgtifname', 'mask', 'dynamicrange', 'nameservers', 'ddnsdomain', 'domain');
foreach (@vnets) {
if ($_->{net} =~ /:/) { #IPv6 detected
if ($_->{net} =~ /:/) {
$usingipv6 = 1;
}
}
# open ipv4 omshell file handles - $OMOUT will contain the response
open2($OMOUT, $OMIN, "/usr/bin/omshell ");
# setup omapi for the connection and check for the node requested
print $OMIN "key "
. $omapiuser . " \""
. $omapikey . "\"\n";
print $OMIN "connect\n";
print $OMIN "new host\n";
# specify which node we are looking up
print $OMIN "set name = \"$node\"\n";
print $OMIN "open\n";
# the close will put the data into $OMOUT
print $OMIN "close\n";
close($OMIN);
my $name = 0;
# Process the output
while (<$OMOUT>) { # now read the output of sort(1)
chomp $_;
# if this line contains the node name
if ($_ =~ $node) {
# save the name returned
if ($name) {
$nname = $_;
$nname =~ s/name = //;
$nname =~ s/"//g;
}
$name = 1;
}
# if this line is the hardware-address line
if ($_ =~ 'hardware-address') {
# save the hardware address as it is with the hardware-address label
$hwaddr = $_;
}
# if this line is the ip-address line
elsif ($_ =~ 'ip-address') {
# convert the hex IP address to a dotted decimal address for readability
my ($ipname, $ip) = split /= /, $_;
chomp($ip);
my ($p1, $p2, $p3, $p4) = split(/\:/, $ip);
my $dp1 = hex($p1);
my $dp2 = hex($p2);
my $dp3 = hex($p3);
my $dp4 = hex($p4);
$ipaddr = "ip-address = $dp1.$dp2.$dp3.$dp4";
}
}
# if we collected the ip address then print out the information for this node
my ($nname, $ipaddr, $hwaddr) = _omshell_query_host($node, $omapiuser, $omapikey, undef);
if ($ipaddr) {
push @{ $rsp->{data} }, "$nname: $ipaddr, $hwaddr";
xCAT::MsgUtils->message("I", $rsp, $callback);
}
close($OMOUT);
# if using IPv6 addresses check using omshell IPv6 port
if ($usingipv6) {
open2($OMOUT6, $OMIN6, "/usr/bin/omshell ");
print $OMOUT6 "port 7912\n";
print $OMOUT6 "connect\n";
print $OMIN6 "key "
. $omapiuser . " \""
. $omapikey . "\"\n";
print $OMIN6 "connect\n";
print $OMIN6 "new host\n";
# check for the node specified
print $OMIN6 "set name = \"$node\"\n";
print $OMIN6 "open\n";
print $OMIN6 "close\n";
close($OMIN6);
$name = 0;
$ipaddr = "";
while (<$OMOUT6>) { # now read the output
chomp $_;
if ($_ =~ $node) {
# save the name
if ($name) {
$nname = $_;
$nname =~ s/name = //;
$nname =~ s/"//g;
}
$name = 1;
}
if ($_ =~ 'hardware-address') {
# save the hardware-address
$hwaddr = $_;
}
elsif ($_ =~ 'ip-address') {
#save the ip address
my ($ipname, $ipaddr) = split /= /, $_;
chomp($ipaddr);
}
}
# print the information if the ip address is found
if ($ipaddr) {
push @{ $rsp->{data} }, "$nname: $ipaddr, $hwaddr";
my ($nname6, $ipaddr6, $hwaddr6) = _omshell_query_host($node, $omapiuser, $omapikey, 7912);
if ($ipaddr6) {
push @{ $rsp->{data} }, "$nname6: $ipaddr6, $hwaddr6";
xCAT::MsgUtils->message("I", $rsp, $callback);
}
# close the IPv6 output file handle
close($OMOUT6);
}
}

View File

@@ -265,6 +265,7 @@ sub setstate {
print $pcfg "exit\n";
}
close($pcfg);
_write_uefi_exit_script($bootloader_root, $node, $cref->{currstate});
} elsif ($kern and $kern->{kernel}) {
if ($kern->{kernel} =~ /!/) { #TODO: deprecate this, do stateless Xen like stateless ESXi
my $hypervisor;
@@ -360,10 +361,21 @@ sub setstate {
} else { #TODO: actually, should possibly default to xCAT image?
print $pcfg "LOCALBOOT 0\n";
close($pcfg);
_write_uefi_exit_script($bootloader_root, $node, $cref->{currstate});
}
return (0, "");
}
sub _write_uefi_exit_script {
my ($bootloader_root, $node, $state) = @_;
open(my $ucfg, '>', "$bootloader_root/$node.uefi");
print $ucfg "#!gpxe\n";
print $ucfg "#$state\n" if $state;
print $ucfg "exit\n";
close($ucfg);
}
my $errored = 0;

View File

@@ -23,6 +23,7 @@ BEGIN
use lib "$::XCATROOT/lib/perl";
use strict;
use xCAT::Utils;
use xCAT::SvrUtils;
use xCAT::DHCP::Backend;
use xCAT::NetworkUtils;
use Getopt::Long;
@@ -1881,52 +1882,30 @@ sub setupLinuxexports
# add tftpboot to /etc/exports - if needed
#
my $cmd = "/bin/cat /etc/exports | grep '$::TFTPDIR'";
my $outref = xCAT::Utils->runcmd("$cmd", -1);
if ($::RUNCMD_RC != 0)
{
# ok - then add this entry
#SECURITY: this has potential for sharing private host/user keys
my $cmd =
"/bin/echo '$::TFTPDIR *(rw,no_root_squash,sync,no_subtree_check)' >> /etc/exports";
my $outref = xCAT::Utils->runcmd("$cmd", 0);
if ($::RUNCMD_RC != 0)
foreach my $exportdir ($::TFTPDIR, $::INSTALLDIR) {
if (!xCAT::SvrUtils->nfs_export_exists($exportdir))
{
xCAT::MsgUtils->message('E',
"Could not update the /etc/exports file.");
my $cmd =
"/bin/echo '$exportdir *(rw,no_root_squash,sync,no_subtree_check,insecure)' >> /etc/exports";
xCAT::Utils->runcmd("$cmd", 0);
if ($::RUNCMD_RC != 0)
{
xCAT::MsgUtils->message('E',
"Could not update the /etc/exports file.");
}
else
{
verbose("Added $exportdir to the /etc/exports file.");
$changed_exports++;
}
}
else
{
verbose("Added $::TFTPDIR to the /etc/exports file.");
$changed_exports++;
}
}
#
# add /install to /etc/exports - if needed
#
$cmd = "/bin/cat /etc/exports | grep '$::INSTALLDIR'";
$outref = xCAT::Utils->runcmd("$cmd", -1);
if ($::RUNCMD_RC != 0)
{
# ok - then add this entry
#SECURITY: this has potential for sharing private host/user keys
my $cmd =
"/bin/echo '$::INSTALLDIR *(rw,no_root_squash,sync,no_subtree_check)' >> /etc/exports";
my $outref = xCAT::Utils->runcmd("$cmd", 0);
if ($::RUNCMD_RC != 0)
{
xCAT::MsgUtils->message('E',
"Could not update the /etc/exports file.");
}
else
{
verbose("Added $::INSTALLDIR to the /etc/exports file.");
$changed_exports++;
if (xCAT::SvrUtils->ensure_nfs_export_option($exportdir, 'insecure'))
{
verbose("Added insecure to existing $exportdir export.");
$changed_exports++;
}
}
}
@@ -1973,8 +1952,8 @@ sub setupLinuxexports
{
verbose("NFS has been enabled.");
}
$cmd = "/usr/sbin/exportfs -a";
$outref = xCAT::Utils->runcmd("$cmd", 0);
my $cmd = "/usr/sbin/exportfs -a";
my $outref = xCAT::Utils->runcmd("$cmd", 0);
if ($::RUNCMD_RC != 0)
{
xCAT::MsgUtils->message('E', "Error with $cmd.");

View File

@@ -205,9 +205,9 @@ base64decode()
# must be the last partition on the disk - this forces us to put swap before
# the main root partition, and forces us to have a fixed size swap partition
#
# Also, the autoinstall.yaml file that this needs to be appended to is a
# modified cloud-init.conf file, with the top level elided - hence the
# storage block below needs to start with no indentation.
# Subiquity re-serializes /autoinstall.yaml after the first load,
# stripping the autoinstall: wrapper. The storage block is appended
# to this unwrapped file and must start at column 0.
logger -t $log_label -p "info" "Generate partition file..."
if [ -e /tmp/xcat.install_disk ]; then
INSTALL_DISK=$(cat /tmp/xcat.install_disk)
@@ -220,42 +220,113 @@ fi
if [ -d /sys/firmware/efi ]; then
cat <<EOF >/tmp/partitionfile
storage:
version: 1
config:
- {id: disk-detected, ptable: gpt, path: $INSTALL_DISK, wipe: superblock,
preserve: false, name: '', grub_device: true, type: disk}
- {id: efi-part, device: disk-detected, size: 512MB, flag: boot,
type: partition, preserve: false}
- {id: swap-part, device: disk-detected, size: 2GB, flag: swap,
type: partition, preserve: false}
- {id: root-part, device: disk-detected, size: -1, type: partition,
preserve: false}
- {id: efi-part-fs, volume: efi-part, type: format, fstype: fat, label: efi}
- {id: swap-part-fs, volume: swap-part, type: format, fstype: swap}
- {id: root-part-fs, volume: root-part, type: format, fstype: ext4,
label: root}
- {id: efi-part-mount, device: efi-part-fs, type: mount, path: /boot/efi}
- {id: swap-part-mount, device: swap-part-fs, type: mount, path: none}
- {id: root-part-mount, device: root-part-fs, type: mount, path: /}
- id: disk-detected
type: disk
ptable: gpt
path: $INSTALL_DISK
wipe: superblock-recursive
preserve: false
- id: efi-part
type: partition
device: disk-detected
size: 512M
flag: boot
number: 1
preserve: false
grub_device: true
- id: swap-part
type: partition
device: disk-detected
size: 2G
number: 2
preserve: false
- id: root-part
type: partition
device: disk-detected
size: -1
wipe: superblock
number: 3
preserve: false
- id: efi-part-fs
type: format
fstype: fat32
volume: efi-part
preserve: false
- id: swap-part-fs
type: format
fstype: swap
volume: swap-part
preserve: false
- id: root-part-fs
type: format
fstype: ext4
volume: root-part
preserve: false
- id: efi-part-mount
type: mount
path: /boot/efi
device: efi-part-fs
- id: swap-part-mount
type: mount
path: none
device: swap-part-fs
- id: root-part-mount
type: mount
path: /
device: root-part-fs
EOF
else
cat <<EOF >/tmp/partitionfile
storage:
version: 1
config:
- {id: disk-detected, ptable: msdos, path: $INSTALL_DISK, wipe: superblock,
preserve: false, name: '', grub_device: true, type: disk}
- {id: boot-part, device: disk-detected, size: 512MB, flag: boot,
type: partition, preserve: false}
- {id: swap-part, device: disk-detected, size: 2GB, flag: swap,
type: partition, preserve: false}
- {id: root-part, device: disk-detected, size: -1, type: partition,
preserve: false}
- {id: boot-part-fs, volume: boot-part, type: format, fstype: ext4, label: boot}
- {id: swap-part-fs, volume: swap-part, type: format, fstype: swap}
- {id: root-part-fs, volume: root-part, type: format, fstype: ext4,
label: root}
- {id: boot-part-mount, device: boot-part-fs, type: mount, path: /boot}
- {id: swap-part-mount, device: swap-part-fs, type: mount, path: none}
- {id: root-part-mount, device: root-part-fs, type: mount, path: /}
- id: disk-detected
type: disk
ptable: gpt
path: $INSTALL_DISK
wipe: superblock-recursive
preserve: false
grub_device: true
- id: bios-grub
type: partition
device: disk-detected
size: 1M
flag: bios_grub
number: 1
preserve: false
- id: swap-part
type: partition
device: disk-detected
size: 2G
number: 2
preserve: false
- id: root-part
type: partition
device: disk-detected
size: -1
wipe: superblock
number: 3
preserve: false
- id: swap-part-fs
type: format
fstype: swap
volume: swap-part
preserve: false
- id: root-part-fs
type: format
fstype: ext4
volume: root-part
preserve: false
- id: swap-part-mount
type: mount
path: none
device: swap-part-fs
- id: root-part-mount
type: mount
path: /
device: root-part-fs
EOF
fi
@@ -265,6 +336,5 @@ fi
exit 0
if [ "$XCATDEBUGMODE" = "1" ] || [ "$XCATDEBUGMODE" = "2" ]; then
then
set +x
fi

View File

@@ -13,65 +13,81 @@ autoinstall:
authorized-keys: []
install-server: true
apt:
primary:
- arches: [amd64, i386]
uri: https://mirror.pnl.gov/ubuntu/
fallback: offline-install
geoip: false
disable_suites:
- updates
- backports
- security
kernel:
package: linux-generic
user-data:
hostname: #HOSTNAME#
disable_root: false
package_update: true
package_upgrade: true
package_update: false
package_upgrade: false
timezone: #TABLE:site:key=timezone:value#
chpasswd:
list:
- root:#CRYPT:passwd:key=system,username=root:password#
list: |
root:#CRYPT:passwd:key=system,username=root:password#
expire: false
packages:
- bash
- nfs-common
- openssl
- isc-dhcp-client
- libc-bin
- openssh-server
- openssh-client
- wget
- vim
- rsync
- busybox-static
- gawk
- dnsutils
- chrony
- gpg
early-commands:
- '{
echo "Running install disk detection...";
wget http://#XCATVAR:XCATMASTER##COLONHTTPPORT#/install/autoinst/getinstdisk;
chmod u+x ./getinstdisk;
./getinstdisk;
echo "Running early_command Installation script...";
wget http://#XCATVAR:XCATMASTER##COLONHTTPPORT#/install/autoinst/#HOSTNAME#.pre;
chmod u+x #HOSTNAME#.pre;
./#HOSTNAME#.pre;
echo "Updating storage config...";
cat /autoinstall.yaml |head -n -1 > /tmp/autoinstall.yaml;
cat /tmp/partitionfile >> /tmp/autoinstall.yaml;
cat /tmp/autoinstall.yaml >/autoinstall.yaml;
echo "Disabling systemd-resolved...";
rm -f /etc/resolv.conf;
echo "nameserver #TABLE:noderes:$NODE:xcatmaster#" >/etc/resolv.conf;
echo "domain #TABLE:site:key=domain:value#" >>/etc/resolv.conf;
} >>/tmp/pre-install.log'
- |
exec >/tmp/pre-install.log 2>&1
set -eux
echo "=== xCAT early-commands ==="
echo "=== network ==="
ip addr show
ip route show
echo "=== disk detection ==="
wget -T 30 -O /tmp/getinstdisk http://#XCATVAR:XCATMASTER##COLONHTTPPORT#/install/autoinst/getinstdisk
test -s /tmp/getinstdisk
chmod u+x /tmp/getinstdisk
/tmp/getinstdisk
echo "=== pre-install script ==="
wget -T 30 -O /tmp/pre.sh http://#XCATVAR:XCATMASTER##COLONHTTPPORT#/install/autoinst/#HOSTNAME#.pre
test -s /tmp/pre.sh
chmod u+x /tmp/pre.sh
/tmp/pre.sh
echo "=== storage injection ==="
test -s /tmp/partitionfile
sed -i '/^\.\.\.$/d' /autoinstall.yaml
cat /tmp/partitionfile >> /autoinstall.yaml
echo "=== DNS setup ==="
rm -f /etc/resolv.conf
echo "nameserver #TABLE:noderes:$NODE:xcatmaster#" >/etc/resolv.conf
echo "domain #TABLE:site:key=domain:value#" >>/etc/resolv.conf
echo "=== early-commands complete ==="
late-commands:
- mkdir -p /target/var/log/xcat/
- '{
cat /tmp/pre-install.log >> /target/var/log/xcat/xcat.log;
installnic="#TABLE:noderes:$NODE:installnic#";
installmac="#TABLE:mac:$NODE:mac#";
installmac="$(printf ''%s'' "${installmac}" | tr ''A-F'' ''a-f'')";
mkdir -p /target/etc/netplan;
printf ''%s\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \"${installmac}\"" " set-name: ${installnic}" " dhcp4: true" >/target/etc/netplan/00-xcat-install.yaml;
chmod 600 /target/etc/netplan/00-xcat-install.yaml;
printf ''%s\n'' ''#HOSTNAME#'' >/target/etc/hostname;
if grep -q ''^127\.0\.1\.1'' /target/etc/hosts; then
sed -i ''s/^127\.0\.1\.1.*/127.0.1.1 #HOSTNAME#/'' /target/etc/hosts;
else
printf ''%s\n'' ''127.0.1.1 #HOSTNAME#'' >>/target/etc/hosts;
fi;
mkdir -p /target/etc/cloud;
touch /target/etc/cloud/cloud-init.disabled;
echo "Updating kernel command line...";
echo "GRUB_CMDLINE_LINUX=\"#TABLE:bootparams:$NODE:kcmdline#\"" >>/target/etc/default/grub;
printf ''%s\n'' ''GRUB_CMDLINE_LINUX="#TABLEBLANKOKAY:bootparams:$NODE:kcmdline#"'' >>/target/etc/default/grub;
curtin in-target --target /target update-grub2;
echo "Running late_command Installation script...";
wget http://#XCATVAR:XCATMASTER##COLONHTTPPORT#/install/autoinst/#HOSTNAME#.post;
wget -T 30 http://#XCATVAR:XCATMASTER##COLONHTTPPORT#/install/autoinst/#HOSTNAME#.post;
chmod u+x #HOSTNAME#.post;
cp ./#HOSTNAME#.post /target/root/post.script;
curtin in-target --target /target /root/post.script;
} >>/target/var/log/xcat/xcat.log 2>&1'
error-commands:
- tar -c --transform='s/^/#HOSTNAME#-logs\//' /var/crash /var/logs/installer |nc -l 8080
- tar -c --transform='s/^/#HOSTNAME#-logs\//' /var/crash /var/log/installer /tmp/pre-install.log /autoinstall.yaml 2>/dev/null |nc -l 8080

View File

@@ -87,6 +87,8 @@ d-i apt-setup/multiverse boolean false
d-i apt-setup/universe boolean false
d-i apt-setup/backports boolean false
d-i apt-setup/updates boolean false
# Keep legacy d-i installs on the local mirror/content only.
d-i apt-setup/services-select multiselect
### Boot loader installation
@@ -143,8 +145,10 @@ d-i preseed/late_command string \
mount -o bind /dev/pts /target/dev/pts -t devpts; \
mount -o bind /sys /target/sys; \
chroot /target /root/post.script; \
if [ -f /target/etc/apt/sources.list ]; then \
sed -i -e '/^[[:space:]]*deb[[:space:]].*[[:space:]][^[:space:]]*-\(security\|updates\|backports\)[[:space:]]/s/^/# /' -e '/^[[:space:]]*deb-src[[:space:]].*[[:space:]][^[:space:]]*-\(security\|updates\|backports\)[[:space:]]/s/^/# /' /target/etc/apt/sources.list; \
fi; \
if [ -f /target/etc/network/interfaces ]; then \
cp /target/etc/network/interfaces /etc/network/interfaces; \
fi; \
} >>/target/var/log/xcat/xcat.log 2>&1

View File

@@ -40,7 +40,10 @@ then
sed -i 's/^X11Forwarding .*$/X11Forwarding yes/' $ROOTDIR/etc/ssh/sshd_config
sed -i 's/^KeyRegenerationInterval .*$/KeyRegenerationInterval 0/' $ROOTDIR/etc/ssh/sshd_config
sed -i 's/\(.*MaxStartups.*\)/#\1/' $ROOTDIR/etc/ssh/sshd_config
echo "MaxStartups 1024" >>$ROOTDIR/etc/ssh/sshd_config
# Keep the unauthenticated connection limit below systemd's default
# 1024 soft open-file limit. Ubuntu 22.04 OpenSSH can otherwise spin
# in ppoll() before sending the SSH banner.
echo "MaxStartups 100:30:200" >>$ROOTDIR/etc/ssh/sshd_config
fi
if [ -r $ROOTDIR/etc/ssh/sshd_config ]
@@ -77,4 +80,3 @@ chmod 600 $ROOTDIR/root/.ssh/config
#chroot $ROOTDIR ssh-keygen -y -f /root/.ssh/id_rsa >/root/.ssh/id_rsa.pub

View File

@@ -16,3 +16,5 @@ gzip
xz-utils
cpio
chrony
dracut
dracut-network

View File

@@ -0,0 +1,21 @@
bash
nfs-common
openssl
isc-dhcp-client
libc-bin
linux-image-generic
openssh-server
openssh-client
wget
rsync
busybox-static
gawk
bind9-dnsutils
tar
gzip
xz-utils
cpio
chrony
iproute2
dracut
dracut-network

View File

@@ -0,0 +1,21 @@
bash
nfs-common
openssl
isc-dhcp-client
libc-bin
linux-image-generic
openssh-server
openssh-client
wget
rsync
busybox-static
gawk
bind9-dnsutils
tar
gzip
xz-utils
cpio
chrony
iproute2
dracut
dracut-network

View File

@@ -1,7 +1,11 @@
#!/bin/sh
echo $drivers
dracut_install wget tar cpio gzip dash modprobe touch echo cut wc xz
dracut_install grep ifconfig hostname awk egrep grep dirname expr
dracut_install grep hostname awk egrep dirname expr ip sed tr cat
dracut_install mount.nfs
inst "$moddir/xcatroot" "/sbin/xcatroot"
inst_hook cmdline 10 "$moddir/xcat-cmdline.sh"
# install netroot dispatcher if available (dracut 051+ with network-legacy)
if [ -f "$moddir/../40network/netroot.sh" ]; then
inst "$moddir/../40network/netroot.sh" "/sbin/netroot"
fi

View File

@@ -1,5 +1,5 @@
#!/bin/sh
echo $drivers
dracut_install wget cpio gzip dash modprobe wc touch echo cut
dracut_install grep ifconfig hostname awk egrep grep dirname expr
dracut_install wget cpio gzip dash modprobe wc touch echo cut
dracut_install grep hostname awk egrep dirname expr
inst_hook pre-pivot 5 "$moddir/xcat-prepivot.sh"

View File

@@ -0,0 +1,14 @@
#!/bin/bash
check() {
return 0
}
depends() {
echo network
return 0
}
install() {
. "$moddir/install"
}

View File

@@ -1,4 +1,7 @@
root=1
rootok=1
netroot=xcat
echo '[ -e $NEWROOT/proc ]' > /initqueue-finished/xcatroot.sh
for HDIR in "" "/lib/dracut/hooks"; do
[ -d "${HDIR}/initqueue-finished" ] || continue
echo '[ -e $NEWROOT/proc ]' > ${HDIR}/initqueue-finished/xcatroot.sh
done

View File

@@ -83,7 +83,10 @@ if [ -d "$NEWROOT/etc/sysconfig" -a ! -e "$NEWROOT/etc/sysconfig/selinux" ]; the
echo "SELINUX=disabled" >> "$NEWROOT/etc/sysconfig/selinux"
fi
# inject new exit_if_exists
echo 'settle_exit_if_exists="--exit-if-exists=/dev/root"; rm "$job"' > /initqueue/xcat.sh
# force udevsettle to break
> /initqueue/work
# signal dracut to proceed with switch_root
[ -e /dev/root ] || ln -s null /dev/root
for HDIR in "" "/lib/dracut/hooks"; do
[ -d "${HDIR}/initqueue" ] || continue
echo 'settle_exit_if_exists="--exit-if-exists=/dev/root"; rm -f -- "$job"' > ${HDIR}/initqueue/xcat.sh
> ${HDIR}/initqueue/work
done

View File

@@ -181,9 +181,14 @@ cd /
if [ -z $STATEMNT ]; then
for lf in /tmp/dhclient.*.lease; do
[ -e "$lf" ] || continue
netif=${lf#*.}
netif=${netif%.*}
cp $lf "$NEWROOT/var/lib/dhclient/dhclient-$netif.leases"
if [ -d "$NEWROOT/var/lib/dhclient" ]; then
cp $lf "$NEWROOT/var/lib/dhclient/dhclient-$netif.leases"
elif [ -d "$NEWROOT/var/lib/dhcp" ]; then
cp $lf "$NEWROOT/var/lib/dhcp/dhclient.$netif.leases"
fi
done
if [ ! -z "$ifname" ]; then
@@ -191,31 +196,58 @@ if [ -z $STATEMNT ]; then
ETHX=${ifname%:$MACX*}
elif [ ! -z "$netdev" ]; then
ETHX=$netdev
MACX=`ip link show $netdev | grep ether | awk '{print $2}'`
if [ -e "/sys/class/net/$netdev/address" ]; then
MACX=$(cat /sys/class/net/$netdev/address)
else
MACX=$(ip link show $netdev 2>/dev/null | grep ether | awk '{print $2}')
fi
elif [ ! -z "$BOOTIF" ]; then
MACX=$BOOTIF
#ETHX=`ifconfig |grep -i $BOOTIF | awk '{print $1}'`
ETHX=` ip -oneline link show |grep -i $BOOTIF|awk -F ':' '{print $2}'|grep -o "[^ ]\+\( \+[^ ]\+\)*"`
MACX=$(echo $BOOTIF | sed 's/^01-//;s/-/:/g' | tr 'A-Z' 'a-z')
for devpath in /sys/class/net/*; do
[ -e "$devpath/address" ] || continue
if [ "$(cat $devpath/address)" = "$MACX" ]; then
ETHX=${devpath##*/}
break
fi
done
[ -z "$ETHX" ] && ETHX=$(ip -oneline link show | grep -i "$MACX" | awk -F ':' '{print $2}' | grep -o "[^ ]\+\( \+[^ ]\+\)*")
fi
if [ ! -z "$MACX" ] && [ ! -z "$ETHX" ]; then
if [ ! -e $NEWROOT/etc/sysconfig/network-scripts/ifcfg-$ETHX ]; then
touch $NEWROOT/etc/sysconfig/network-scripts/ifcfg-$ETHX
if [ -x $NEWROOT/usr/sbin/netplan ]; then
mkdir -p $NEWROOT/etc/netplan
cat > $NEWROOT/etc/netplan/90-xcat.yaml <<XCATNETPLAN
network:
version: 2
ethernets:
$ETHX:
dhcp4: true
match:
macaddress: "$MACX"
XCATNETPLAN
elif [ -d $NEWROOT/etc/sysconfig/network-scripts ]; then
echo "DEVICE=$ETHX" > $NEWROOT/etc/sysconfig/network-scripts/ifcfg-$ETHX
echo "BOOTPROTO=dhcp" >> $NEWROOT/etc/sysconfig/network-scripts/ifcfg-$ETHX
echo "HWADDR=$MACX" >> $NEWROOT/etc/sysconfig/network-scripts/ifcfg-$ETHX
echo "ONBOOT=yes" >> $NEWROOT/etc/sysconfig/network-scripts/ifcfg-$ETHX
elif [ -d $NEWROOT/etc/network ]; then
echo "auto $ETHX" >> $NEWROOT/etc/network/interfaces
echo "iface $ETHX inet dhcp" >> $NEWROOT/etc/network/interfaces
fi
echo "DEVICE=$ETHX" > $NEWROOT/etc/sysconfig/network-scripts/ifcfg-$ETHX
echo "BOOTPROTO=dhcp" >> $NEWROOT/etc/sysconfig/network-scripts/ifcfg-$ETHX
echo "HWADDR=$MACX" >> $NEWROOT/etc/sysconfig/network-scripts/ifcfg-$ETHX
echo "ONBOOT=yes" >> $NEWROOT/etc/sysconfig/network-scripts/ifcfg-$ETHX
fi
fi
cp /etc/resolv.conf "$NEWROOT/etc/"
if [ -d "$NEWROOT/etc/sysconfig" -a ! -e "$NEWROOT/etc/sysconfig/selinux" ]; then
echo "SELINUX=disabled" >> "$NEWROOT/etc/sysconfig/selinux"
fi
# inject new exit_if_exists
echo 'settle_exit_if_exists="--exit-if-exists=/dev/root"; rm "$job"' > /initqueue/xcat.sh
# force udevsettle to break
> /initqueue/work
# signal dracut to proceed with switch_root
[ -e /dev/root ] || ln -s null /dev/root
for HDIR in "" "/lib/dracut/hooks"; do
[ -d "${HDIR}/initqueue" ] || continue
echo 'settle_exit_if_exists="--exit-if-exists=/dev/root"; rm -f -- "$job"' > ${HDIR}/initqueue/xcat.sh
> ${HDIR}/initqueue/work
done

View File

@@ -357,7 +357,7 @@ unless ($onlyinitrd) {
open($aptconfig, ">", "$rootimg_dir/etc/apt/sources.list");
if ($srcdir) {
print $aptconfig "deb http://$masternode:$httpport$srcdir $dist main\n";
print $aptconfig "deb http://$masternode:$httpport$srcdir $dist main restricted universe\n";
}
foreach (@pkgdir_internet) {
@@ -369,9 +369,19 @@ unless ($onlyinitrd) {
close($aptconfig);
# Prevent package maintainer scripts from starting services while the
# image root is mounted as a chroot.
fake_policy_rc_d();
$rc = system("$aptgetcmd");
if ($rc) {
xdie "Failed to update package metadata\n";
}
# run apt-get upgrade to update any installed debs
my $aptgetcmd_update = $aptgetcmd . "&&" . $aptgetcmdby . " upgrade ";
$rc = system("$aptgetcmd_update");
if (!$noupdate) {
$rc = system("$aptgetcmdby upgrade");
}
# Start to install pkgs in pkglist
unless ($imagename) {
@@ -463,6 +473,9 @@ unless ($onlyinitrd) {
}
my $aptgetcmd_install = $aptgetcmdby . " install --no-install-recommends " . $kernelimage;
$rc = system("$aptgetcmd_install");
if ($rc) {
xdie "Failed to install kernel package $kernelimage\n";
}
}
}
@@ -713,7 +726,9 @@ my @moddeps = <$moddeps>;
my @checkdeps = @ndrivers;
while (scalar @checkdeps) {
my $driver = pop @checkdeps;
my @lines = grep /\/$driver:/, @moddeps;
my $driver_match = quotemeta($driver);
$driver_match =~ s/\\\.ko$/\\.ko(?:\\.(?:gz|xz|zst))?/;
my @lines = grep /\/$driver_match:/, @moddeps;
foreach (@lines) {
chomp;
s/.*://;
@@ -722,6 +737,7 @@ while (scalar @checkdeps) {
my $dep;
foreach $dep (@deps) {
$dep =~ s/.*\///;
$dep =~ s/\.(?:gz|xz|zst)$//;
unless (grep { $_ eq $dep } @ndrivers) { #only add if not added
unshift(@checkdeps, $dep); #recursively check dependencies
unshift(@ndrivers, $dep);
@@ -731,18 +747,12 @@ while (scalar @checkdeps) {
}
}
close($moddeps);
if (-d "$rootimg_dir/usr/share/dracut") {
if (-d "$rootimg_dir/usr/share/dracut" or -d "$rootimg_dir/usr/lib/dracut") {
$dracutmode = 1;
# get dracut version
my $dracutver = `chroot $rootimg_dir dpkg-query -W dracut | awk '{print \$2}'| aek -F- {print \$1}'`;
if ($dracutver =~ /^\d\d\d$/) {
if ($dracutver >= "009") {
$dracutdir = "dracut_009";
} else {
$dracutdir = "dracut"; # The default directory
}
}
my $dracutver = `chroot $rootimg_dir dpkg-query -W dracut 2>/dev/null | awk '{print \$2}'`;
chomp($dracutver);
$dracutdir = "dracut";
print "Enter the dracut mode. Dracut version: $dracutver. Dracut directory: $dracutdir.\n";
}
@@ -896,7 +906,7 @@ sub getlibs {
foreach (@libs) {
# vdso is a shared library that's embedded in the kernel
# automatically loaded whenever a new process is exec-ed
if ( $_ =~ /linux-vdso64/ ) {
if ( $_ =~ /linux-vdso/ ) {
next;
}
if (/statically linked/ or /not a dynamic executable/) {
@@ -922,12 +932,22 @@ sub getlibs {
sub mkinitrd_dracut {
my ($mode) = @_; # the mode is for statelite or stateless
my $dracutmpath = "$rootimg_dir/usr/share/dracut/modules.d/97xcat";
my $dracutmpath;
if (-d "$rootimg_dir/usr/lib/dracut/modules.d") {
$dracutmpath = "$rootimg_dir/usr/lib/dracut/modules.d/97xcat";
} else {
$dracutmpath = "$rootimg_dir/usr/share/dracut/modules.d/97xcat";
}
mkpath($dracutmpath);
my $perm = (stat("$fullpath/$dracutdir/check"))[2];
cp("$fullpath/$dracutdir/check", $dracutmpath);
chmod($perm & 07777, "$dracutmpath/check");
if (-f "$fullpath/$dracutdir/module-setup.sh") {
$perm = (stat("$fullpath/$dracutdir/module-setup.sh"))[2];
cp("$fullpath/$dracutdir/module-setup.sh", $dracutmpath);
chmod($perm & 07777, "$dracutmpath/module-setup.sh");
}
foreach (@ndrivers) { s/\.ko$//; }
@@ -945,9 +965,13 @@ sub mkinitrd_dracut {
$perm = (stat("$fullpath/$dracutdir/xcat-prepivot.sh"))[2];
chmod($perm & 07777, "$dracutmpath/xcat-prepivot.sh");
# update etc/dracut.conf
# update etc/dracut.conf - use network-legacy if 40network requires wicked
my $netmod = "network";
if (-d "$dracutmpath/../35network-legacy" && ! -x "$rootimg_dir/usr/sbin/wicked") {
$netmod = "network-legacy";
}
open($DRACUTCONF, '>', "$rootimg_dir/etc/dracut.conf");
print $DRACUTCONF qq{dracutmodules+=" xcat nfs base network kernel-modules "\n};
print $DRACUTCONF qq{dracutmodules+=" xcat nfs base $netmod kernel-modules "\n};
print $DRACUTCONF qq{add_drivers+=" $add_drivers "\n};
print $DRACUTCONF qq{filesystems+=" nfs "\n};
close $DRACUTCONF;
@@ -975,9 +999,13 @@ sub mkinitrd_dracut {
$perm = (stat("$fullpath/$dracutdir/installkernel"))[2];
chmod($perm & 07777, "$dracutmpath/installkernel");
# update etc/dracut.conf
# update etc/dracut.conf - use network-legacy if 40network requires wicked
my $netmod = "network";
if (-d "$dracutmpath/../35network-legacy" && ! -x "$rootimg_dir/usr/sbin/wicked") {
$netmod = "network-legacy";
}
open($DRACUTCONF, '>', "$rootimg_dir/etc/dracut.conf");
print $DRACUTCONF qq{dracutmodules+=" xcat nfs base network kernel-modules "\n};
print $DRACUTCONF qq{dracutmodules+=" xcat nfs base $netmod kernel-modules "\n};
print $DRACUTCONF qq{add_drivers+=" $add_drivers "\n};
close $DRACUTCONF;
} else {
@@ -1152,7 +1180,7 @@ EOS1
#print $inifile "mknod /dev/hvc7 c 229 7\n";
foreach (@ndrivers) {
print $inifile "insmod /lib/$_\n";
print $inifile "[ -f /lib/$_ ] && insmod /lib/$_\n";
}
@@ -1188,13 +1216,24 @@ PRINIC=$prinic
NODESTATUS='y'
XCATIPORT="3002"
iface_exists() {
[ -n "\$1" ] && [ -d "/sys/class/net/\$1" ]
}
for i in `cat /proc/cmdline`; do
KEY=`echo \$i |awk -F= '{print \$1}'`
if [ "\$KEY" == 'netdev' ]; then
NETDEV=`echo \$i |awk -F= '{print \$2}'`
elif [ "\$KEY" == 'BOOTIF' ]; then
VALUE=`echo \$i |awk -F= '{print \$2}'|sed -e s/^01-// -e s/-/:/g`
BOOTIF=`ifconfig -a|grep -i "hwaddr \$VALUE"|awk '{print \$1}'`
VALUE=`echo \$i |awk -F= '{print \$2}'|sed -e s/^01-// -e s/-/:/g|tr 'A-Z' 'a-z'`
for devpath in /sys/class/net/*; do
[ -e "\$devpath/address" ] || continue
if [ "\`cat \$devpath/address\`" = "\$VALUE" ]; then
BOOTIF=\${devpath##*/}
break
fi
done
[ -z "\$BOOTIF" ] && BOOTIF=`ifconfig -a|grep -i "hwaddr \$VALUE"|awk '{print \$1}'`
elif [ "\$KEY" == 'XCAT' ]; then
VALUE=`echo \$i |awk -F= '{print \$2}'`
# format: XCAT=xcatmaster:3001
@@ -1215,16 +1254,23 @@ for i in `cat /proc/cmdline`; do
done
if [ -z "\$IFACE" ]; then
if [ ! -z "\$NETDEV" ]; then
IFACE=\$NETDEV
elif [ ! -z "\$BOOTIF" ]; then
if iface_exists "\$BOOTIF"; then
IFACE=\$BOOTIF
elif [ ! -z "\$PRINIC" ]; then
elif iface_exists "\$NETDEV"; then
IFACE=\$NETDEV
elif iface_exists "\$PRINIC"; then
IFACE=\$PRINIC
else
echo "\${RED}Couldn't find the proper booting device, switch to shell...\${RESET}"
shell
exit
for devpath in /sys/class/net/*; do
IFACE=\${devpath##*/}
[ "\$IFACE" != "lo" ] && break
IFACE=
done
if [ -z "\$IFACE" ]; then
echo "\${RED}Couldn't find the proper booting device, switch to shell...\${RESET}"
shell
exit
fi
fi
fi
@@ -1666,9 +1712,10 @@ EOMS
}
# add extra commands for initrd
foreach ("usr/bin/dig", "bin/busybox", "bin/bash", "sbin/mount.nfs", "usr/bin/rsync", "sbin/insmod", "sbin/udevadm", "sbin/modprobe", "sbin/blkid", "sbin/depmod", "usr/bin/wget", "usr/bin/xz", "bin/gzip", "bin/tar") {
getlibs($_);
push @filestoadd, $_;
foreach my $dest ("usr/bin/dig", "bin/busybox", "bin/bash", "sbin/mount.nfs", "usr/bin/rsync", "sbin/insmod", "sbin/udevadm", "sbin/modprobe", "sbin/blkid", "sbin/depmod", "usr/bin/wget", "usr/bin/xz", "bin/gzip", "bin/tar") {
my $src = find_rootimg_file($dest);
getlibs($src);
push @filestoadd, [ $src, $dest ];
}
# Workaround for Ubuntu 18.04 busybox bug
@@ -1691,16 +1738,16 @@ EOMS
if ($arch =~ /x86_64/) {
push @filestoadd, "lib64/libnss_dns.so.2";
#the path of libnss_dns.so.2 for ubuntu 14.4+
push @filestoadd, "lib/x86_64-linux-gnu/libnss_dns.so.2";
push @filestoadd, "lib64/libresolv.so.2";
foreach ("lib64/libnss_dns.so.2", "lib/x86_64-linux-gnu/libnss_dns.so.2", "lib64/libresolv.so.2") {
push @filestoadd, $_ if (-e "$rootimg_dir/$_");
}
} elsif ($arch =~ /ppc64el/) {
push @filestoadd, "lib/powerpc64le-linux-gnu/libnss_files.so.2";
push @filestoadd, "lib/powerpc64le-linux-gnu/libnss_dns.so.2";
foreach ("lib/powerpc64le-linux-gnu/libnss_files.so.2", "lib/powerpc64le-linux-gnu/libnss_dns.so.2") {
push @filestoadd, $_ if (-e "$rootimg_dir/$_");
}
} else {
push @filestoadd, "lib/libnss_dns.so.2";
push @filestoadd, "lib/libnss_dns.so.2" if (-e "$rootimg_dir/lib/libnss_dns.so.2");
}
push @filestoadd, keys %libhash;
@@ -1716,9 +1763,7 @@ EOMS
} elsif (-f "$pathtofiles/" . $_->[0]) {
$srcpath = "$pathtofiles/" . $_->[0];
}
mkpath(dirname("/tmp/xcatinitrd.$$/" . $_->[1]));
copy($srcpath, "/tmp/xcatinitrd.$$/" . $_->[1]);
chmod 0755, "/tmp/xcatinitrd.$$/" . $_->[1];
copy_initrd_file($srcpath, "/tmp/xcatinitrd.$$/" . $_->[1]);
} else {
#print "$_\n";
@@ -1728,9 +1773,7 @@ EOMS
} elsif (-f "$pathtofiles/$_") {
$srcpath = "$pathtofiles/$_";
}
mkpath(dirname("/tmp/xcatinitrd.$$/$_"));
copy("$srcpath", "/tmp/xcatinitrd.$$/$_");
chmod 0755, "/tmp/xcatinitrd.$$/" . $_;
copy_initrd_file($srcpath, "/tmp/xcatinitrd.$$/$_");
}
}
@@ -1743,7 +1786,7 @@ EOMS
system("cp $rootimg_dir/lib/modules/$kernelver/modules.order /tmp/xcatinitrd.$$/lib/modules/$kernelver/modules.order");
}
system("chroot /tmp/xcatinitrd.$$/ depmod $kernelver");
system("chroot /tmp/xcatinitrd.$$/ /sbin/depmod $kernelver");
# Copy udev and network scripts into initrd for s390x, which also works for other platforms
# udev
@@ -1789,7 +1832,9 @@ sub isaptdir {
sub isnetdriver {
foreach (@ndrivers) {
if ($File::Find::name =~ /\/$_/) {
my $driver_match = quotemeta($_);
$driver_match =~ s/\\\.ko$/\\.ko(?:\\.(?:gz|xz|zst))?/;
if ($File::Find::name =~ /\/$driver_match$/) {
my $filetoadd = $File::Find::name;
$filetoadd =~ s!$rootimg_dir/!!;
push @filestoadd, [ $filetoadd, "lib/$_" ];
@@ -1797,6 +1842,41 @@ sub isnetdriver {
}
}
sub find_rootimg_file {
my ($file) = @_;
return $file if (-e "$rootimg_dir/$file");
my $base = basename($file);
foreach my $candidate ("usr/bin/$base", "bin/$base", "usr/sbin/$base", "sbin/$base") {
return $candidate if (-e "$rootimg_dir/$candidate");
}
xdie "Failed to find $file in $rootimg_dir\n";
}
sub copy_initrd_file {
my ( $src, $dest ) = @_;
mkpath(dirname($dest));
if ( $src =~ /\.ko\.(gz|xz|zst)$/ ) {
my %decompressor = (
gz => [ 'gzip', '-dc' ],
xz => [ 'xz', '-dc' ],
zst => [ 'zstd', '-dc' ],
);
my @cmd = ( @{ $decompressor{$1} }, $src );
open( my $in, '-|', @cmd ) || xdie "Failed to decompress $src\n";
open( my $out, '>', $dest ) || xdie "Failed to open $dest\n";
copy( $in, $out ) || xdie "Failed to copy $src to $dest\n";
close($out);
close($in) || xdie "Failed to decompress $src\n";
} else {
copy( $src, $dest ) || xdie "Failed to copy $src to $dest\n";
}
chmod 0755, $dest;
}
sub postscripts {
generic_post();
@@ -2187,5 +2267,3 @@ sub usage {
return 0;
}

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use File::Find;
# This test verifies that copycds does not leave empty Packages files
# alongside valid Packages.gz files. An empty Packages file causes
# apt's cdrom handler to fail with a hash mismatch during autoinstall.
my $installdir = $ENV{INSTALLDIR} || '/install';
plan skip_all => "$installdir not found" unless -d $installdir;
my @empty_packages;
my @ok_packages;
find(sub {
return unless $_ eq 'Packages';
return unless -f $File::Find::name;
my $gz = "$File::Find::name.gz";
if (-s $File::Find::name == 0 && -f $gz && -s $gz > 0) {
push @empty_packages, $File::Find::name;
} elsif (-s $File::Find::name > 0) {
push @ok_packages, $File::Find::name;
}
}, "$installdir");
if (@empty_packages || @ok_packages) {
is(scalar @empty_packages, 0,
'no empty Packages files alongside valid Packages.gz')
or diag("Empty Packages files found:\n " . join("\n ", @empty_packages));
cmp_ok(scalar @ok_packages, '>', 0,
'at least one non-empty Packages file exists')
if @ok_packages;
} else {
plan skip_all => "no apt repo trees found under $installdir";
}
done_testing();

View File

@@ -49,6 +49,12 @@ is(
'Ubuntu 22.04 defaults to Kea'
);
is(
xCAT::DHCP::Backend->default_backend( platform => '', os => 'ubuntu22.04.5', os_name => 'ubuntu', version => '22.04.5' ),
'kea',
'Ubuntu 22.04 point releases default to Kea'
);
is(
xCAT::DHCP::Backend->default_backend( platform => '', os => 'ubuntu20.04', os_name => 'ubuntu', version => '20.04' ),
'isc',

View File

@@ -76,7 +76,8 @@ is_deeply( $config->{Dhcp4}{'interfaces-config'}{interfaces}, ['eth0'], 'interfa
is( $config->{Dhcp4}{'valid-lifetime'}, 600, 'valid lifetime is rendered' );
is( $config->{Dhcp4}{'lease-database'}{type}, 'memfile', 'memfile lease backend is the default' );
is( $config->{Dhcp4}{'reservations-in-subnet'}, JSON::true, 'subnet host reservations are enabled by default' );
is( $config->{Dhcp4}{'reservations-out-of-pool'}, JSON::true, 'out-of-pool host reservations are enabled by default' );
is( $config->{Dhcp4}{'reservations-out-of-pool'}, JSON::true, 'out-of-pool host reservations are enabled for xCAT static addresses' );
is( $config->{Dhcp4}{'match-client-id'}, JSON::false, 'DHCPv4 leases match MAC reservations when client-id changes across boot stages' );
my $subnet = $config->{Dhcp4}{subnet4}[0];
is( $subnet->{id}, 1, 'subnet id is rendered' );
@@ -191,8 +192,9 @@ is( $empty_boot_subnet->{'boot-file-name'}, '', 'empty boot-file-name is preserv
my $reservation_policy_json = $backend->render_dhcp4_config(
{
'reservations-in-subnet' => 1,
'reservations-out-of-pool' => 1,
'reservations-in-subnet' => 0,
'reservations-out-of-pool' => 0,
'match-client-id' => 1,
subnets => [
{
id => 9,
@@ -203,8 +205,9 @@ my $reservation_policy_json = $backend->render_dhcp4_config(
}
);
my $reservation_policy_config = decode_json($reservation_policy_json);
is( $reservation_policy_config->{Dhcp4}{'reservations-in-subnet'}, JSON::true, 'reservation in-subnet policy can be overridden' );
is( $reservation_policy_config->{Dhcp4}{'reservations-out-of-pool'}, JSON::true, 'reservation out-of-pool policy can be overridden' );
is( $reservation_policy_config->{Dhcp4}{'reservations-in-subnet'}, JSON::false, 'reservation in-subnet policy can be overridden' );
is( $reservation_policy_config->{Dhcp4}{'reservations-out-of-pool'}, JSON::false, 'reservation out-of-pool policy can be overridden' );
is( $reservation_policy_config->{Dhcp4}{'match-client-id'}, JSON::true, 'client-id lease matching policy can be overridden' );
my $comment_dir = tempdir(CLEANUP => 1);
my $commented_config = "$comment_dir/kea-dhcp4.conf";

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../../xCAT-server/lib";
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
use lib "$FindBin::Bin/../../perl-xCAT";
use Test::More;
$ENV{XCATCFG} ||= 'SQLite:/tmp';
my $source_dhcp_plugin = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/dhcp.pm";
if ( -f $source_dhcp_plugin ) {
require $source_dhcp_plugin;
} else {
require xCAT_plugin::dhcp;
}
my ($name, $ip, $mac) = xCAT_plugin::dhcp::_parse_omshell_host_output(
'node01',
'name = "node01"',
'ip-address = 0a:00:00:05',
'hardware-address = 00:11:22:33:44:55',
);
is($name, 'node01', 'host name is parsed');
is($ip, 'ip-address = 10.0.0.5', 'IPv4 OMAPI hex address is converted to dotted decimal');
is($mac, 'hardware-address = 00:11:22:33:44:55', 'hardware address is preserved');
($name, $ip, $mac) = xCAT_plugin::dhcp::_parse_omshell_host_output(
'nodev6',
'name = "nodev6"',
'ip-address = 2001:db8::50',
'hardware-address = 00:aa:bb:cc:dd:ee',
);
is($name, 'nodev6', 'IPv6 host name is parsed');
is($ip, 'ip-address = 2001:db8::50', 'IPv6 address is preserved');
is($mac, 'hardware-address = 00:aa:bb:cc:dd:ee', 'IPv6 hardware address is preserved');
done_testing();

View File

@@ -0,0 +1,50 @@
#!/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 File::Temp qw/tempdir/;
use Test::More;
use xCAT::SvrUtils;
my $dir = tempdir(CLEANUP => 1);
my $exports = "$dir/exports";
open(my $fh, '>', $exports) or die "Unable to write $exports: $!";
print $fh <<'EOF';
# comment
/install *(rw,no_root_squash,sync,no_subtree_check) # install tree
/tftpboot -rw,no_root_squash
/other host1(rw) host2(ro,insecure)
EOF
close($fh);
ok(xCAT::SvrUtils->nfs_export_exists('/install', files => [$exports]), 'target export is detected');
ok(!xCAT::SvrUtils->nfs_export_exists('/missing', files => [$exports]), 'missing export is not detected');
ok(
xCAT::SvrUtils->ensure_nfs_export_option('/install', 'insecure', files => [$exports]),
'missing option is added to target export'
);
my $content = do { local $/; open(my $rfh, '<', $exports) or die $!; <$rfh> };
like($content, qr{^/install \*\(rw,no_root_squash,sync,no_subtree_check,insecure\) # install tree$}m, 'target export gains insecure and keeps comment');
like($content, qr{^/tftpboot -rw,no_root_squash$}m, 'non-target export is unchanged');
like($content, qr{^/other host1\(rw\) host2\(ro,insecure\)$}m, 'unrelated multi-client export is unchanged');
ok(
!xCAT::SvrUtils->ensure_nfs_export_option('/install', 'insecure', files => [$exports]),
'existing option is not duplicated'
);
ok(
xCAT::SvrUtils->ensure_nfs_export_option('/tftpboot', 'insecure', files => [$exports]),
'dash-style export gains option'
);
$content = do { local $/; open(my $rfh, '<', $exports) or die $!; <$rfh> };
like($content, qr{^/tftpboot -rw,no_root_squash,insecure$}m, 'dash-style export is updated correctly');
done_testing();

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../../xCAT-probe/lib/perl";
use Test::More;
require probe_utils;
ok(
probe_utils::dhcp_query_reply_matches(
'xcatmntest: ip-address = 10.10.0.254, hardware-address = aa:aa:aa:aa:aa:aa',
'xcatmntest',
'10.10.0.254',
'aa:aa:aa:aa:aa:aa',
),
'ISC-style makedhcp query output with comma matches'
);
ok(
probe_utils::dhcp_query_reply_matches(
'xcatmntest: ip-address = 10.10.0.254 hardware-address = aa:aa:aa:aa:aa:aa',
'xcatmntest',
'10.10.0.254',
'aa:aa:aa:aa:aa:aa',
),
'Kea-style makedhcp query output without comma matches'
);
is(
probe_utils::dhcp_query_reply_mac(
'node01: ip-address = 192.0.2.10 hardware-address = AA:BB:CC:DD:EE:FF',
'node01',
'192.0.2.10',
),
'AA:BB:CC:DD:EE:FF',
'DHCP query MAC is extracted without normalizing display case'
);
ok(
!probe_utils::dhcp_query_reply_matches(
'xcatmntest: ip-address = 10.10.0.99 hardware-address = aa:aa:aa:aa:aa:aa',
'xcatmntest',
'10.10.0.254',
'aa:aa:aa:aa:aa:aa',
),
'mismatched IP does not match'
);
done_testing();

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../../xCAT-probe/lib/perl";
use Test::More;
require probe_utils;
my %netplan = (
'ethernets.eth0' => 'renderer: networkd',
'ethernets.eth0.addresses' => "- 10.0.0.2/24\n",
'ethernets.eth0.dhcp4' => 'false',
'ethernets.eth1' => 'renderer: networkd',
'ethernets.eth1.addresses' => "- 10.0.0.3/24\n",
'ethernets.eth1.dhcp4' => 'true',
'vlans.bond0\.123' => 'renderer: networkd',
'vlans.bond0\.123.addresses' => "- 10.0.123.5/24\n",
);
{
no warnings 'redefine';
local *probe_utils::_command_available = sub { return $_[0] eq 'netplan' ? 1 : 0; };
local *probe_utils::_netplan_get = sub { return $netplan{ $_[0] }; };
ok(probe_utils::_netplan_has_static_ip('eth0', '10.0.0.2'), 'static netplan address is detected');
ok(!probe_utils::_netplan_has_static_ip('eth0', '10.0.0.99'), 'wrong address is not treated as static');
ok(!probe_utils::_netplan_has_static_ip('eth1', '10.0.0.3'), 'dhcp4 true is not treated as static');
ok(probe_utils::_netplan_has_static_ip('bond0.123', '10.0.123.5'), 'dotted VLAN interface is escaped for netplan get');
}
done_testing();

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
my $script_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/share/xcat/netboot/add-on/statelite/add_ssh" : '';
$script_path = "xCAT-server/share/xcat/netboot/add-on/statelite/add_ssh"
unless -f $script_path;
plan skip_all => "add_ssh not found" unless -f $script_path;
my $script = do { local $/; open my $fh, '<', $script_path or die $!; <$fh> };
like($script, qr/MaxStartups 100:30:200/, 'add_ssh caps MaxStartups below systemd soft nofile limit');
unlike($script, qr/echo\s+"MaxStartups 1024"/, 'add_ssh does not force MaxStartups 1024');
done_testing();

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
my $tmpl_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/share/xcat/install/ubuntu/compute.tmpl" : '';
$tmpl_path = "xCAT-server/share/xcat/install/ubuntu/compute.tmpl"
unless -f $tmpl_path;
plan skip_all => "compute.tmpl not found" unless -f $tmpl_path;
my $tmpl = do { local $/; open my $fh, '<', $tmpl_path or die $!; <$fh> };
my $template_pm_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/lib/perl/xCAT/Template.pm" : '';
$template_pm_path = "xCAT-server/lib/perl/xCAT/Template.pm"
unless -f $template_pm_path;
like($tmpl, qr/^d-i apt-setup\/multiverse boolean false$/m, 'legacy Ubuntu preseed disables multiverse');
like($tmpl, qr/^d-i apt-setup\/universe boolean false$/m, 'legacy Ubuntu preseed disables universe');
like($tmpl, qr/^d-i apt-setup\/backports boolean false$/m, 'legacy Ubuntu preseed disables backports');
like($tmpl, qr/^d-i apt-setup\/updates boolean false$/m, 'legacy Ubuntu preseed disables release updates');
like($tmpl, qr/^d-i apt-setup\/services-select multiselect\s*$/m, 'legacy Ubuntu preseed disables security/update services for offline installs');
unlike($tmpl, qr/^d-i apt-setup\/services-select multiselect .*\S/m, 'legacy Ubuntu preseed does not select any external apt services');
like($tmpl, qr/sed -i .*security.*updates.*backports.*\/target\/etc\/apt\/sources\.list/s,
'legacy Ubuntu late command comments disabled apt service suites in the installed target');
SKIP: {
skip "Template.pm not found", 3 unless -f $template_pm_path;
my $template_pm = do { local $/; open my $fh, '<', $template_pm_path or die $!; <$fh> };
like($template_pm, qr/\$ENV\{HTTPPORT\} \|\| \$ENV\{httpport\} \|\| '80'/,
'legacy Ubuntu mirror spec uses the rendered HTTP port for local mirrors');
like($template_pm, qr/d-i apt-setup\/security_host string \$security_host/,
'legacy Ubuntu mirror spec redirects installer security host to rendered xCAT master');
like($template_pm, qr/d-i apt-setup\/security_path string \$pkgdir/,
'legacy Ubuntu mirror spec redirects installer security path to the local pkgdir');
}
done_testing();

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
my $plugin_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/lib/perl/xCAT_plugin/debian.pm" : '';
$plugin_path = "xCAT-server/lib/xcat/plugins/debian.pm"
unless -f $plugin_path;
plan skip_all => "debian.pm not found" unless -f $plugin_path;
my $src = do { local $/; open my $fh, '<', $plugin_path or die $!; <$fh> };
like($src, qr/autoinstall ip=dhcp netboot=nfs/, 'subiquity bootparams enable autoinstall');
like($src, qr/ds=nocloud-net;s=http:\/\//, 'subiquity bootparams point at NoCloud seed');
like($src, qr/\$kcmdline\s*\.=\s*" ---";/, 'subiquity bootparams end with installer argument separator');
done_testing();

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
my $pre_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/share/xcat/install/scripts/pre.ubuntu.subiquity" : '';
$pre_path = "xCAT-server/share/xcat/install/scripts/pre.ubuntu.subiquity"
unless -f $pre_path;
plan skip_all => "pre.ubuntu.subiquity not found" unless -f $pre_path;
my $script = do { local $/; open my $fh, '<', $pre_path or die $!; <$fh> };
# Shell syntax check
my $rc = system("bash -n $pre_path 2>/dev/null");
is($rc, 0, 'pre.ubuntu.subiquity passes bash -n syntax check');
# UEFI storage layout checks
like($script, qr/if \[ -d \/sys\/firmware\/efi \]/, 'script detects UEFI via /sys/firmware/efi');
# UEFI: grub_device on EFI partition, NOT on disk
like($script, qr/id: efi-part.*grub_device: true/s, 'UEFI: grub_device on EFI partition');
# UEFI: EFI partition formatted as fat32
like($script, qr/id: efi-part-fs.*fstype: fat32/s, 'UEFI: EFI partition formatted fat32');
# UEFI: EFI partition mounted at /boot/efi
like($script, qr/efi-part-mount.*path: \/boot\/efi/s, 'UEFI: EFI partition mounted at /boot/efi');
# BIOS storage layout checks
like($script, qr/id: bios-grub.*flag: bios_grub/s, 'BIOS: has bios_grub partition');
like($script, qr/id: disk-detected.*grub_device: true/s, 'BIOS: grub_device on disk');
# Both paths must have storage: version: 1
my @storage_version = ($script =~ /storage:\s*\n\s*version:\s*1/g);
is(scalar @storage_version, 2, 'both UEFI and BIOS have storage: version: 1');
# Both paths write to /tmp/partitionfile
my @partfile = ($script =~ /\/tmp\/partitionfile/g);
cmp_ok(scalar @partfile, '>=', 2, 'both paths write to /tmp/partitionfile');
# Storage at column 0 (for re-serialized autoinstall.yaml)
like($script, qr/^storage:\n version: 1/m, 'storage block starts at column 0');
done_testing();

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
my $tmpl_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/share/xcat/install/ubuntu/compute.subiquity.tmpl" : '';
$tmpl_path = "xCAT-server/share/xcat/install/ubuntu/compute.subiquity.tmpl"
unless -f $tmpl_path;
plan skip_all => "compute.subiquity.tmpl not found" unless -f $tmpl_path;
my $tmpl = do { local $/; open my $fh, '<', $tmpl_path or die $!; <$fh> };
like($tmpl, qr/^#cloud-config/, 'template starts with #cloud-config');
like($tmpl, qr/autoinstall:/, 'template has autoinstall: key');
like($tmpl, qr/version:\s*1/, 'template has version: 1');
unlike($tmpl, qr/^\s*identity:/m, 'template must not have identity section (use user-data instead)');
like($tmpl, qr/kernel:/, 'template has kernel section');
like($tmpl, qr/package:\s*linux-generic/, 'template specifies linux-generic kernel');
like($tmpl, qr/apt:/, 'template has apt section');
like($tmpl, qr/fallback:\s*offline-install/, 'template uses offline-install fallback');
unlike($tmpl, qr/WARN: no partitionfile/, 'template does not silently fall back when xCAT pre-script fails');
unlike($tmpl, qr/INSTALL_DISK=""/, 'template does not guess an install disk in early-commands');
like($tmpl, qr/geoip:\s*false/, 'template disables geoip');
like($tmpl, qr/ssh:/, 'template has ssh section');
like($tmpl, qr/install-server:\s*true/, 'template enables ssh install-server');
unlike($tmpl, qr/package_update:\s*true/, 'template does not enable package_update');
unlike($tmpl, qr/^\s*-\s+nfs-common\s*$/m, 'template does not require nfs-common from offline ISO packages');
# YAML safety: use printf with single-quoted arguments instead of shell-specific
# escape sequences. dash does not portably interpret printf \xNN.
unlike($tmpl, qr/echo.*GRUB_CMDLINE.*\\"/, 'no escaped double quotes in echo GRUB line');
unlike($tmpl, qr/\\\\x22/, 'template does not rely on non-portable printf hex escapes');
like($tmpl, qr/printf ''%s\\n'' ''GRUB_CMDLINE_LINUX="#TABLEBLANKOKAY:bootparams:\$NODE:kcmdline#"''/, 'GRUB line uses portable printf quoting');
like($tmpl, qr/\/target\/etc\/netplan\/00-xcat-install\.yaml/, 'template writes an xCAT-owned target netplan file');
like($tmpl, qr/installnic="#TABLE:noderes:\$NODE:installnic#"/, 'target netplan uses node installnic');
like($tmpl, qr/installmac="#TABLE:mac:\$NODE:mac#"/, 'target netplan uses node MAC');
like($tmpl, qr/installmac="\$\(printf ''%s'' "\$\{installmac\}" \| tr ''A-F'' ''a-f''\)"/, 'target netplan normalizes MAC case');
like($tmpl, qr/printf ''%s\\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \\"\$\{installmac\}\\"" " set-name: \$\{installnic\}" " dhcp4: true" >\/target\/etc\/netplan\/00-xcat-install\.yaml;/, 'target netplan printf stays on one shell line');
like($tmpl, qr/" macaddress: \\"\$\{installmac\}\\""/, 'target netplan matches by MAC address');
like($tmpl, qr/" set-name: \$\{installnic\}"/, 'target netplan sets the expected installnic name');
like($tmpl, qr/"\s+dhcp4: true"/, 'target netplan enables DHCPv4 on installnic');
like($tmpl, qr/printf ''%s\\n'' ''#HOSTNAME#'' >\/target\/etc\/hostname/, 'template writes target hostname before disabling cloud-init');
like($tmpl, qr/sed -i ''s\/\^127\\\.0\\\.1\\\.1\.\*\/127\.0\.1\.1 #HOSTNAME#\/'' \/target\/etc\/hosts/, 'template updates target hosts entry for hostname');
like($tmpl, qr/touch \/target\/etc\/cloud\/cloud-init\.disabled/, 'target cloud-init is disabled after target netplan is written');
# Regression: downloaded files are required and checked with test -s before use.
like($tmpl, qr/wget -T 30 -O \/tmp\/getinstdisk http:\/\/#XCATVAR:XCATMASTER#/, 'getinstdisk download is required');
like($tmpl, qr/test -s \/tmp\/getinstdisk/, 'getinstdisk checked with -s not -x');
like($tmpl, qr/wget -T 30 -O \/tmp\/pre\.sh http:\/\/#XCATVAR:XCATMASTER#/, 'pre.sh download is required');
like($tmpl, qr/test -s \/tmp\/pre\.sh/, 'pre.sh checked with -s not -x');
like($tmpl, qr/test -s \/tmp\/partitionfile/, 'partitionfile from pre-script is required');
unlike($tmpl, qr/wget .*?\|\| true/, 'xCAT control artifact downloads are not masked');
unlike($tmpl, qr/if \[ -x \/tmp\/getinstdisk \]/, 'getinstdisk not checked with -x');
unlike($tmpl, qr/if \[ -x \/tmp\/pre\.sh \]/, 'pre.sh not checked with -x');
# Regression: disable_suites must be release-independent (no noble-*, jammy-*, etc.)
unlike($tmpl, qr/noble-|jammy-|focal-/, 'disable_suites uses release-independent names');
like($tmpl, qr/- updates/, 'disable_suites includes updates');
like($tmpl, qr/- backports/, 'disable_suites includes backports');
like($tmpl, qr/- security/, 'disable_suites includes security');
done_testing();

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
my $xnba_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/lib/perl/xCAT_plugin/xnba.pm" : '';
$xnba_path = "xCAT-server/lib/xcat/plugins/xnba.pm"
unless -f $xnba_path;
plan skip_all => "xnba.pm not found" unless -f $xnba_path;
my $src = do { local $/; open my $fh, '<', $xnba_path or die $!; <$fh> };
# iPXE only treats a standalone ; token as a command separator.
# A ; embedded inside an argument value (e.g. ds=nocloud-net;s=...)
# is NOT split by iPXE's parser. Therefore xnba.pm must NOT escape
# the semicolon — doing so (e.g. \;) would corrupt the value and
# prevent cloud-init from parsing the NoCloud seed URL.
unlike($src, qr/kcmd.*=~.*s\/;/, 'BIOS path does not escape semicolons');
unlike($src, qr/ucmd.*=~.*s\/;/, 'UEFI path does not escape semicolons');
# The kcmdline is passed directly to imgargs without modification
like($src, qr/imgargs kernel.*\$kern->\{kcmdline\}/, 'BIOS kcmdline passed directly to imgargs');
# UEFI nodes must not keep a stale install script when the node moves
# back to boot/standby; otherwise they PXE back into the installer.
like($src, qr/sub _write_uefi_exit_script\b/, 'UEFI local boot helper exists');
like($src, qr/_write_uefi_exit_script\(\$bootloader_root, \$node, \$cref->\{currstate\}\);/, 'boot/local states rewrite UEFI xNBA script');
like($src, qr/print \$ucfg "exit\\n";/, 'UEFI local boot script exits iPXE to firmware');
done_testing();