2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-04 20:17:55 +00:00

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>
This commit is contained in:
Daniel Hilst
2026-08-24 14:40:13 -03:00
parent 1b81af0d1d
commit 067eda810e
6 changed files with 175 additions and 9 deletions
+138
View File
@@ -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;
+12 -2
View File
@@ -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 " .
@@ -16,6 +16,7 @@ gzip
xz-utils
cpio
chrony
util-linux-extra
iproute2
dracut
dracut-network
+2 -2
View File
@@ -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
+19 -5
View File
@@ -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"
+3
View File
@@ -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