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

test(setupntp): timesyncd is only stopped on the chrony path

setupntp stops and disables systemd-timesyncd so it does not discipline the
clock against the daemon being configured. That block sits ~24 lines below the
`exec setupntp.traditional` the ntpd path takes, and setupntp.traditional never
mentions timesyncd -- so on the ntpd path the disable is never reached and both
end up stepping the clock.

setupntp resets PATH at the top, so PATH stubs cannot shadow anything in it;
the region is extracted and driven with systemctl shadowed by a shell function,
which bash resolves ahead of PATH. `exec` is a builtin and cannot be shadowed,
so the harness puts a recording stand-in where the script execs -- the run
ending there is the behaviour under test.

Five of the eight assertions pass already, pinning the chrony path and the
hand-off itself. The three that fail are the ntpd path.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
Daniel Hilst
2026-09-01 21:00:40 -03:00
parent 124e2782db
commit fdeafa3e3f
@@ -0,0 +1,108 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Spec;
use File::Temp qw(tempdir);
use FindBin;
use Test::More;
# Regression: systemd-timesyncd is only stopped on the chrony path.
#
# setupntp disciplines the clock with whichever daemon it configures, and timesyncd is an SNTP
# client that disciplines the same clock. Leaving it running means two things stepping the clock.
# The stop/disable sits ~24 lines BELOW the `exec setupntp.traditional` that the ntpd path takes,
# and setupntp.traditional never touches timesyncd -- so on the ntpd path it is never reached.
#
# setupntp resets PATH at the top, so PATH stubs cannot shadow anything in it. The region that
# decides this is extracted instead and driven with systemctl and friends shadowed by shell
# functions, which bash resolves ahead of PATH. `exec` is a builtin and cannot be shadowed, so
# the harness puts a recording stand-in where the script execs, and the exec ends the run --
# which is exactly the behaviour under test.
my $repo_root = File::Spec->rel2abs(
File::Spec->catdir( $FindBin::Bin, '..', '..' )
);
my $setupntp = File::Spec->catfile( $repo_root, 'xCAT', 'postscripts', 'setupntp' );
plan skip_all => "setupntp not found" unless -f $setupntp;
my $src = do { local $/; open my $fh, '<', $setupntp or die $!; <$fh> };
# From the ntpd dispatch through the timesyncd disable: the ordering between them is the point.
# BAIL_OUT rather than skip, so a rename fails loudly instead of silently covering nothing.
my ($region) = $src =~ /\n(if \[ -n "\$\{USE_NTPD\}" \]\n.*?systemctl disable systemd-timesyncd\.service 2>\/dev\/null\n)/s;
BAIL_OUT('could not extract the ntpd-dispatch/timesyncd region from setupntp')
unless defined $region;
my $dir = tempdir( CLEANUP => 1 );
my $run = 0;
sub drive {
my ($use_ntpd) = @_;
$run++;
my $root = File::Spec->catdir( $dir, "run$run" );
mkdir $root;
my $calls = File::Spec->catfile( $root, 'calls' );
# The script execs "${0%/*}/setupntp.traditional"; $0 is the harness, so this is where it
# lands. Recording rather than executing anything real.
my $trad = File::Spec->catfile( $root, 'setupntp.traditional' );
open my $t, '>', $trad or die $!;
print $t "#!/bin/bash\necho 'EXEC setupntp.traditional' >> '$calls'\nexit 0\n";
close $t;
chmod 0755, $trad;
my $harness = File::Spec->catfile( $root, 'harness.sh' );
open my $fh, '>', $harness or die $!;
print $fh <<"PRE";
#!/bin/bash
USE_NTPD='$use_ntpd'
NTP_SERVERS=(pool.ntp.org)
log_label=xcat
logger(){ :; }
systemctl(){ echo "systemctl \$*" >> '$calls'; return 0; }
timedatectl(){ echo "timedatectl \$*" >> '$calls'; return 0; }
check_exec_or_exit(){ :; }
check_executes(){ return 0; }
PRE
print $fh $region, "\n";
close $fh;
system( '/bin/bash', $harness );
return '' unless -f $calls;
return do { local $/; open my $c, '<', $calls or die $!; <$c> };
}
# The chrony path: this already worked, and pins the behaviour we are extending.
my $chrony = drive('');
like( $chrony, qr/^systemctl stop systemd-timesyncd\.service$/m,
'chrony path: timesyncd is stopped' );
like( $chrony, qr/^systemctl disable systemd-timesyncd\.service$/m,
'chrony path: timesyncd is disabled' );
unlike( $chrony, qr/^EXEC setupntp\.traditional$/m,
'chrony path: does not hand off to setupntp.traditional' );
# The ntpd path: it hands off, so anything that must happen has to happen first.
my $ntpd = drive('yes');
like( $ntpd, qr/^EXEC setupntp\.traditional$/m,
'ntpd path: hands off to setupntp.traditional' );
like( $ntpd, qr/^systemctl stop systemd-timesyncd\.service$/m,
'ntpd path: timesyncd is stopped too' );
like( $ntpd, qr/^systemctl disable systemd-timesyncd\.service$/m,
'ntpd path: timesyncd is disabled too' );
my ($before_exec) = $ntpd =~ /\A(.*?)^EXEC setupntp\.traditional/ms;
ok( defined $before_exec && $before_exec =~ /systemctl disable systemd-timesyncd/,
'ntpd path: the disable happens before the hand-off, not after it' );
# setupntp.traditional does not do it either, which is why the ordering above matters.
my $trad_src = File::Spec->catfile(
$repo_root, 'xCAT', 'postscripts', 'setupntp.traditional' );
SKIP: {
skip 'setupntp.traditional not found', 1 unless -f $trad_src;
my $t = do { local $/; open my $h, '<', $trad_src or die $!; <$h> };
unlike( $t, qr/timesyncd/,
'setupntp.traditional does not handle timesyncd itself' );
}
done_testing();