From 067eda810ec05545bdb2241e6027e7fd0fd7ea95 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Mon, 24 Aug 2026 14:40:13 -0300 Subject: [PATCH] fix(xcat-core): configure a server-capable NTP daemon on Ubuntu MNs Add xCAT::NTP::Backend, a selector for the NTP daemon xCAT configures, in the same spirit as xCAT::DHCP::Backend. It honours site.ntpbackend, defaults per distro family (EL7+/SLES15+ chrony, older ntpd, Ubuntu/Debian chrony), and downgrades to whichever of chrony/ntpd is actually installed rather than installing a second daemon. makentp selects through it instead of probing /usr/sbin/chronyd directly, so the choice is one unit-tested code path. Guarantee the daemon at install time: the xcat metapackage now Depends on "chrony | ntp" and the xCAT rpm Requires "(chrony or ntp)". Both sit beside the existing service dependencies the metapackage already declares -- isc-dhcp-server|kea, apache2, nfs-kernel-server -- because an MN that cannot serve time cannot serve its compute nodes, and Ubuntu's default systemd-timesyncd is a client only. Stop requiring hwclock in setupntp. It only persists the already-stepped system clock to the RTC, and Ubuntu 24.04 moved it to util-linux-extra, absent on minimal images -- so a fatal check_exec_or_exit aborted the entire NTP setup, including the clock step that does not use it. Use it when present, log and continue when not, and pull util-linux-extra through Recommends and the diskless pkglist so it usually is. Disable systemd-timesyncd there too. It ships as part of systemd rather than a time-daemon package, so nothing displaces it, and it keeps disciplining the clock against the daemon being configured. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- perl-xCAT/xCAT/NTP/Backend.pm | 138 ++++++++++++++++++ xCAT-server/lib/xcat/plugins/makentp.pm | 14 +- .../ubuntu/compute.ubuntu24.04.x86_64.pkglist | 1 + xCAT/debian/control | 4 +- xCAT/postscripts/setupntp | 24 ++- xCAT/xCAT.spec | 3 + 6 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 perl-xCAT/xCAT/NTP/Backend.pm diff --git a/perl-xCAT/xCAT/NTP/Backend.pm b/perl-xCAT/xCAT/NTP/Backend.pm new file mode 100644 index 000000000..71b5f868a --- /dev/null +++ b/perl-xCAT/xCAT/NTP/Backend.pm @@ -0,0 +1,138 @@ +package xCAT::NTP::Backend; + +# Selector for the NTP daemon xCAT configures on a node (chrony vs. ntpd), in the same spirit as +# xCAT::DHCP::Backend (ISC vs. Kea). makentp/setupntp support chronyd and ntpd only -- there is no +# systemd-timesyncd path (timesyncd is an SNTP client and cannot serve time to compute nodes), so an +# xCAT MN always needs chrony or ntpd. This module centralizes and unit-tests the choice. + +use strict; +use warnings; + +my %valid_backend = map { $_ => 1 } qw(auto chrony ntpd); + +# Accept common aliases so site.ntpbackend / callers can say chronyd or ntp. +my %alias = ( + chronyd => 'chrony', + ntp => 'ntpd', + ntpsec => 'ntpd', +); + +sub normalize { + my ( $class, $backend ) = @_; + + $backend = 'auto' unless defined($backend) && $backend ne ''; + $backend =~ s/^\s+|\s+$//g; + $backend = lc($backend); + $backend = $alias{$backend} if exists $alias{$backend}; + + return $backend if $valid_backend{$backend}; + return; +} + +# choose: resolve the effective backend for this node. +# requested -- override (default: site.ntpbackend, else 'auto') +# os_name/version -- OS identity (default: detected) +# available -- optional { chrony => 0/1, ntpd => 0/1 } to bypass command detection (tests) +# check_available -- when true, downgrade chrony->ntpd (or ntpd->chrony) if the chosen one is +# absent but the other is present, and flag install=1 when neither is present. +sub choose { + my ( $class, %args ) = @_; + + my $requested = exists $args{requested} ? $args{requested} : $class->_site_backend(); + my $normalized = $class->normalize($requested); + unless ($normalized) { + return { error => "Invalid site.ntpbackend value '$requested'. Valid values are auto, chrony, and ntpd." }; + } + + my $selected = $normalized eq 'auto' ? $class->default_backend(%args) : $normalized; + my $result = { requested => $normalized, name => $selected, install => 0 }; + + return $result unless $args{check_available}; + + my $other = $selected eq 'chrony' ? 'ntpd' : 'chrony'; + if ( $class->available( $selected, %args ) ) { + return $result; + } elsif ( $class->available( $other, %args ) ) { + # respect what is actually installed rather than installing a second daemon + $result->{name} = $other; + $result->{downgraded} = $selected; + return $result; + } + + # neither present: keep the preferred choice and tell the caller to install it + $result->{install} = 1; + return $result; +} + +# default_backend: table-driven per distro family. +# EL/RHEL & clones: >= 7 -> chrony, 6 -> ntpd +# SLES/SUSE: >= 15 -> chrony, 12 -> ntpd +# Ubuntu/Debian: chrony (timesyncd is the OOB client but cannot serve; chrony from 18.04+) +sub default_backend { + my ( $class, %args ) = @_; + + my $os_name = exists $args{os_name} ? $args{os_name} : $class->_osver('os'); + my $version = exists $args{version} ? $args{version} : ( split /,/, $class->_osver('all'), 2 )[1]; + my ($major) = ( defined($version) ? $version : '' ) =~ /^(\d+)/; + + if ( defined($os_name) && $os_name =~ /^(?:rhel|rhels|rocky|alma|centos|ol|fedora)$/i ) { + return 'ntpd' if defined($major) && $major <= 6; + return 'chrony'; + } + if ( defined($os_name) && $os_name =~ /^(?:sles|sled|suse|opensuse|leap)$/i ) { + return 'ntpd' if defined($major) && $major <= 12; + return 'chrony'; + } + if ( defined($os_name) && $os_name =~ /^(?:ubuntu|debian)$/i ) { + return 'chrony'; + } + + return 'chrony'; +} + +sub available { + my ( $class, $backend, %args ) = @_; + + if ( exists $args{available} && ref( $args{available} ) eq 'HASH' && exists $args{available}{$backend} ) { + return $args{available}{$backend} ? 1 : 0; + } + + return _command_exists('chronyd') if $backend eq 'chrony'; + return _command_exists('ntpd') if $backend eq 'ntpd'; + return 0; +} + +sub _site_backend { + my $backend = eval { + require xCAT::TableUtils; + return xCAT::TableUtils->get_site_attribute( 'ntpbackend', 'auto' ); + }; + + return $backend || 'auto'; +} + +sub _osver { + my ( $class, $type ) = @_; + + my $osver = eval { + require xCAT::Utils; + return defined($type) ? xCAT::Utils->osver($type) : xCAT::Utils->osver(); + }; + + return $osver || 'unknown'; +} + +sub _command_exists { + my ($command) = @_; + + foreach my $dir ( split /:/, $ENV{PATH} || '' ) { + next unless $dir; + return 1 if -x "$dir/$command"; + } + foreach my $path ( "/usr/sbin/$command", "/usr/bin/$command", "/sbin/$command", "/bin/$command" ) { + return 1 if -x $path; + } + return 0; +} + +1; diff --git a/xCAT-server/lib/xcat/plugins/makentp.pm b/xCAT-server/lib/xcat/plugins/makentp.pm index 41212ebb6..620a45e85 100644 --- a/xCAT-server/lib/xcat/plugins/makentp.pm +++ b/xCAT-server/lib/xcat/plugins/makentp.pm @@ -252,9 +252,19 @@ sub process_request { $ntp_servers = $retdata->{'master'}; } + # Pick the NTP daemon (chrony vs ntpd) via the shared, unit-tested selector -- the same spirit + # as xCAT::DHCP::Backend (ISC vs Kea). It honors site.ntpbackend, defaults per distro, and + # downgrades chrony->ntpd (or vice versa) to whichever is actually installed. + require xCAT::NTP::Backend; + my $ntp_backend = xCAT::NTP::Backend->choose(check_available => 1); + if ($ntp_backend->{error}) { + send_msg(\%request, 1, $ntp_backend->{error}); + return 1; + } + my $have_systemctl = (-x "/usr/bin/systemctl" || -x "/bin/systemctl"); + # Handle chronyd here, - if (-x "/usr/sbin/chronyd" && - (-x "/usr/bin/systemctl" || -x "/bin/systemctl")) { + if ($ntp_backend->{name} eq 'chrony' && $have_systemctl) { send_msg(\%request, 0, "Will configure chronyd instead."); my $cmd = "/install/postscripts/setupntp " . diff --git a/xCAT-server/share/xcat/netboot/ubuntu/compute.ubuntu24.04.x86_64.pkglist b/xCAT-server/share/xcat/netboot/ubuntu/compute.ubuntu24.04.x86_64.pkglist index baa088bac..4c6b2b3c4 100644 --- a/xCAT-server/share/xcat/netboot/ubuntu/compute.ubuntu24.04.x86_64.pkglist +++ b/xCAT-server/share/xcat/netboot/ubuntu/compute.ubuntu24.04.x86_64.pkglist @@ -16,6 +16,7 @@ gzip xz-utils cpio chrony +util-linux-extra iproute2 dracut dracut-network diff --git a/xCAT/debian/control b/xCAT/debian/control index 0481919e6..74ff70c64 100644 --- a/xCAT/debian/control +++ b/xCAT/debian/control @@ -9,8 +9,8 @@ Homepage: https://xcat.org/ Package: xcat Architecture: amd64 ppc64el -Depends: ${perl:Depends}, goconserver(>= 0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, libxml-parser-perl, rsync, tftpd-hpa, libnet-telnet-perl, xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) -Recommends: net-tools, nmap, kea, tftp-hpa, ipmitool-xcat (>= 1.8.17-1), syslinux[any-amd64], libsys-virt-perl, syslinux-xcat, xnba-undi, elilo-xcat, xcat-buildkit (>= 2.13-snap000000000000), xcat-probe (>= 2.13-snap000000000000), xcat-genesis-openembedded-x86-64, xcat-genesis-openembedded-ppc64le, xcat-genesis-openembedded-riscv64 +Depends: ${perl:Depends}, goconserver(>= 0.3.3-snap000000000000), xcat-server (>= 2.13-snap000000000000), xcat-client (>= 2.13-snap000000000000), libdbd-sqlite3-perl, isc-dhcp-server | kea, bind9, apache2, nfs-kernel-server, libxml-parser-perl, rsync, tftpd-hpa, libnet-telnet-perl, chrony | ntp, xcat-genesis-scripts-amd64 (>= 2.13-snap000000000000) +Recommends: net-tools, nmap, kea, tftp-hpa, ipmitool-xcat (>= 1.8.17-1), syslinux[any-amd64], libsys-virt-perl, syslinux-xcat, xnba-undi, elilo-xcat, util-linux-extra, xcat-buildkit (>= 2.13-snap000000000000), xcat-probe (>= 2.13-snap000000000000), xcat-genesis-openembedded-x86-64, xcat-genesis-openembedded-ppc64le, xcat-genesis-openembedded-riscv64 Suggests: yaboot-xcat Description: Metapackage for a common, default xCAT setup xCAT is Extreme Cluster/Cloud Administration Toolkit. xCAT offers complete diff --git a/xCAT/postscripts/setupntp b/xCAT/postscripts/setupntp index eaae4a8b0..7de945790 100755 --- a/xCAT/postscripts/setupntp +++ b/xCAT/postscripts/setupntp @@ -146,7 +146,10 @@ unset MASTER unset NTPSERVERS check_exec_or_exit cp cat logger grep -check_exec_or_exit systemctl timedatectl hwclock +# hwclock is NOT required: it only persists the (already chronyd-stepped) system clock to the RTC +# below, and it is optional -- on Ubuntu 24.04+ it moved to util-linux-extra, which is absent on +# minimal cloud images. Requiring it here aborted the whole NTP setup (and the clock step) on noble. +check_exec_or_exit systemctl timedatectl systemctl stop ntp.service 2>/dev/null systemctl disable ntp.service 2>/dev/null @@ -156,6 +159,13 @@ systemctl disable ntpd.service 2>/dev/null systemctl disable ntp-wait.service 2>/dev/null systemctl disable ntpdate.service 2>/dev/null +# systemd-timesyncd is an SNTP client that also disciplines the system clock. It is part of systemd +# (not a "time-daemon" package), so it coexists with chrony/ntpd at the package level but must be +# stopped or it fights the NTP daemon we are configuring. This is the default time-sync stack on +# Ubuntu, where it must yield to chrony (timesyncd cannot serve time to compute nodes). +systemctl stop systemd-timesyncd.service 2>/dev/null +systemctl disable systemd-timesyncd.service 2>/dev/null + # On Ubuntu 18.04 systemctl stop chrony.service 2>/dev/null # On RHEL 7, 8 @@ -177,10 +187,14 @@ chronyd -f /dev/null -q "$( fi )" -rm -f /etc/adjtime -# Set the hardware clock from the system clock -hwclock --systohc --utc -warn_if_bad "$?" "Failed to set the hardware clock" +# Set the hardware clock from the system clock, when hwclock is available (optional; see above). +if command -v hwclock >/dev/null 2>&1; then + rm -f /etc/adjtime + hwclock --systohc --utc + warn_if_bad "$?" "Failed to set the hardware clock" +else + logger -t $log_label -p local4.info "setupntp: hwclock not present; skipping RTC persist (system clock already set via chronyd)" +fi ## On RHEL 8 #CHRONY_USER="chrony" diff --git a/xCAT/xCAT.spec b/xCAT/xCAT.spec index febac8836..8772828b9 100644 --- a/xCAT/xCAT.spec +++ b/xCAT/xCAT.spec @@ -73,6 +73,9 @@ Requires: httpd nfs-utils nmap bind perl(CGI) # on RHEL7, need to specify it explicitly Requires: net-tools Requires: /usr/bin/killall +# makentp/setupntp configure the MN as an NTP server for its compute nodes and support chronyd/ntpd +# only. chrony is the default on EL7+/SLES15+ (and the only option on EL8+); ntp covers the rest. +Requires: (chrony or ntp) # DHCP backend resolved at INSTALL time (not build time) via an RPM rich # dependency, so a single flat xcat-core build is correct on every EL: el10+ # dropped ISC dhcp from its distro and uses Kea; el8/el9 use ISC dhcpd. SLES