diff --git a/xCAT-server/share/xcat/install/ubuntu/service.subiquity.tmpl b/xCAT-server/share/xcat/install/ubuntu/service.subiquity.tmpl new file mode 100644 index 000000000..ffd5b545d --- /dev/null +++ b/xCAT-server/share/xcat/install/ubuntu/service.subiquity.tmpl @@ -0,0 +1,137 @@ +#cloud-config +autoinstall: + version: 1 + keyboard: {layout: us, toggle: null, variant: ''} + locale: en_US + network: + version: 2 + ethernets: + xcat-install: + match: + name: "e*" + dhcp4: true + ssh: + allow-pw: true + authorized-keys: [] + install-server: true + identity: + realname: 'xCAT Admin' + username: xcatadm + hostname: #HOSTNAME# + password: "#CRYPTORLOCKED:passwd:key=system,username=root:password#" +#UBUNTU_SUBIQUITY_APT_CONFIG# + kernel: + package: linux-generic + user-data: + hostname: #HOSTNAME# + disable_root: false + package_update: false + package_upgrade: false + timezone: #TABLE:site:key=timezone:value# + chpasswd: + list: + - "root:#CRYPTORLOCKED:passwd:key=system,username=root:password#" + expire: false + packages: + - openssh-server + - openssh-client + - wget + - vim + - rsync + - busybox-static + - gawk + - bind9-dnsutils + - chrony + - gpg + - #INCLUDE_DEFAULT_PKGLIST_AUTOINSTALL# + early-commands: + - | + 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 ===" + # glibc's resolver discards a nameserver line naming a host, so writing the xcatmaster + # *name* leaves the installer -- and the in-target apt that inherits this file -- with no + # DNS. Resolve it here, while the live installer's DHCP resolv.conf still works, and keep + # that file when the name does not resolve. A name written back is the case this step + # exists to prevent. + xcatmaster_host="#TABLE:noderes:$NODE:xcatmaster#" + xcatmaster_ip="$(getent ahostsv4 "$xcatmaster_host" | awk '{print $1; exit}')" + if [ -n "$xcatmaster_ip" ]; then + rm -f /etc/resolv.conf + echo "nameserver $xcatmaster_ip" >/etc/resolv.conf + else + echo "xcat: $xcatmaster_host has no IPv4 address; keeping the resolver DHCP supplied" + fi + 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="#SUBIQUITYINSTALLNIC#"; + installmac="#SUBIQUITYINSTALLMAC#"; + mkdir -p /target/etc/netplan; + if [ -z "${installnic}" ]; then + printf ''%s\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \"${installmac}\"" " dhcp4: true" " dhcp4-overrides:" " use-domains: true" >/target/etc/netplan/00-xcat-install.yaml; + else + printf ''%s\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \"${installmac}\"" " set-name: ${installnic}" " dhcp4: true" " dhcp4-overrides:" " use-domains: true" >/target/etc/netplan/00-xcat-install.yaml; + fi; + 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..."; + 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 -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' + # The installer's sources for the otherpkgs repository and the pkgdir mirrors, and the apt + # configuration that kept recommended packages out, served the install; ospkgs and otherpkgs + # write their own after the first boot. A separate item, so the status of the post script above + # still decides whether the install goes on. + - rm -f /target/etc/apt/sources.list.d/xcat-otherpkgs-*.list /target/etc/apt/sources.list.d/xcat-otherpkgs-*.sources /target/etc/apt/sources.list.d/xcat-pkgdir-*.list /target/etc/apt/sources.list.d/xcat-pkgdir-*.sources /target/etc/apt/apt.conf.d/94curtin-config + # Flip the node to local-disk boot, or it PXE-loops back into the installer on reboot. + # xcatd's install monitor greets with "ready", then answers "next" with "done" and runs + # "nodeset next". Require both tokens: another service on that port is not a flipped + # node. The monitor listens on site.xcatiport; TABLEBLANKOKAY because an absent key fails a + # plain TABLE lookup, and with it the whole template. + # Retry, and log a failure rather than reinstall silently. Both reads are bounded: a monitor + # that accepts and never answers would block a bare read and hang the install here. A + # monitor that died and never came back is #7759. + - ['bash', '-c', 'xm=#XCATVAR:XCATMASTER#; port="#TABLEBLANKOKAY:site:key=xcatiport:value#"; [ -n "$port" ] || port=3002; ok=0; for i in 1 2 3 4 5; do if exec 3<>/dev/tcp/$xm/$port; then if read -r -t 10 hello <&3 && [ "$hello" = "ready" ]; then printf "next\n" >&3; if read -r -t 10 ack <&3 && [ "$ack" = "done" ]; then ok=1; fi; fi; exec 3>&- 3<&-; [ "$ok" = 1 ] && break; fi; sleep 5; done; if [ "$ok" != 1 ]; then echo "xcat: FAILED to flip $(hostname) to local-disk boot via $xm:$port; the node will PXE back into the installer" >>/target/var/log/xcat/xcat.log; fi; exit 0'] + error-commands: + # Subiquity waits for every error command to return. "nc -l 8080" waits for a collector, which + # an unattended install does not have. Keep the archive on the installer, and print the end of + # the curtin log to the console, which the management node records. + # + # Both destinations are read from the environment so a test can point them at its own + # directory. The installer sets neither, so an install writes where it always did. + - ['sh', '-c', 'tar -c --ignore-failed-read --transform="s/^/#HOSTNAME#-logs\//" /var/crash /var/log/installer /tmp/pre-install.log /autoinstall.yaml >"${XCAT_ERROR_ARCHIVE:-/run/#HOSTNAME#-logs.tar}" 2>/dev/null; exit 0'] + - ['sh', '-c', 'tail -n 80 /var/log/installer/curtin-install.log >"${XCAT_ERROR_CONSOLE:-/dev/console}" 2>/dev/null; exit 0'] diff --git a/xCAT-test/unit/ubuntu_service_subiquity_template.t b/xCAT-test/unit/ubuntu_service_subiquity_template.t new file mode 100644 index 000000000..3922a4ba3 --- /dev/null +++ b/xCAT-test/unit/ubuntu_service_subiquity_template.t @@ -0,0 +1,61 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use Test::More; + +# xCAT decides that an Ubuntu osimage uses the Subiquity installer from the NAME of the template +# it resolved: xCAT_plugin::debian::using_subiquity matches /subiquity/ in the path. That decision +# controls the netboot kernel command line. A template whose name does not match it makes xCAT +# boot the live-server initrd with the debian-installer preseed arguments, without boot=casper and +# without nfsroot, and the node never finds a live filesystem. +# +# The compute profile has compute.subiquity.tmpl. The service profile had only service.tmpl, so an +# Ubuntu 24.04 service node took the preseed path. + +use lib "$FindBin::Bin/../../perl-xCAT"; +use lib "$FindBin::Bin/../../xCAT-server/lib/perl"; +use xCAT::SvrUtils; + +my $plugin = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/debian.pm"; +plan skip_all => 'debian.pm not found' unless -r $plugin; +eval { require $plugin; 1 } or plan skip_all => "could not load debian.pm: $@"; + +my $share = "$FindBin::Bin/../../xCAT-server/share/xcat/install/ubuntu"; +plan skip_all => "$share not found" unless -d $share; + +# get_file_name takes genos as its last argument. update_tables_with_templates passes 'subiquity' +# for every Ubuntu 20.04 and later osimage. +sub resolved { + my ($profile, $osver, $genos) = @_; + return xCAT::SvrUtils::get_tmpl_file_name($share, $profile, $osver, 'x86_64', $genos); +} + +for my $profile (qw(compute service)) { + my $tmpl = resolved($profile, 'ubuntu24.04', 'subiquity'); + ok(defined $tmpl && -r $tmpl, "$profile: Ubuntu 24.04 resolves an install template"); + ok(xCAT_plugin::debian::using_subiquity('ubuntu24.04', $tmpl), + "$profile: the Ubuntu 24.04 template xCAT resolved is a Subiquity one"); +} + +# Ubuntu 18.04 predates Subiquity and keeps the preseed path. genos stays the os version there. +for my $profile (qw(compute service)) { + my $tmpl = resolved($profile, 'ubuntu18.04', 'ubuntu18.04'); + ok(defined $tmpl && -r $tmpl, "$profile: Ubuntu 18.04 resolves an install template"); + ok(!xCAT_plugin::debian::using_subiquity('ubuntu18.04', $tmpl), + "$profile: Ubuntu 18.04 keeps the debian-installer template"); +} + +# The service template must carry the autoinstall document Subiquity reads. A file named for +# Subiquity that holds a preseed would satisfy using_subiquity and still not install. +my $service = resolved('service', 'ubuntu24.04', 'subiquity'); +SKIP: { + skip 'no service template resolved', 3 unless defined $service && -r $service; + my $text = do { local $/; open my $fh, '<', $service or die "$service: $!"; <$fh> }; + like($text, qr/^#cloud-config/, 'the service template is a cloud-config document'); + like($text, qr/^autoinstall:/m, '... carrying an autoinstall section'); + unlike($text, qr/^d-i /m, '... and no debian-installer preseed directives'); +} + +done_testing();