2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-22 15:48:14 +00:00

test(packaging): exercise Apache configuration upgrades

This commit is contained in:
Vinícius Ferrão
2026-10-09 03:22:46 -03:00
parent 3abd11220a
commit c69f9fb2b4
4 changed files with 97 additions and 154 deletions
+2 -2
View File
@@ -44,5 +44,5 @@ jobs:
dnf --enablerepo=${{ matrix.repository }} install -y perl perl-Capture-Tiny perl-File-Slurper perl-Test-Simple rpm-build bubblewrap systemd chkconfig initscripts tar gzip which diffutils procps-ng
- name: Enable lifecycle test namespaces
run: sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
- name: Test RPM service lifecycle
run: su -s /bin/sh nobody -c 'prove -v xCAT-test/native/xcatd_rpm_lifecycle.t'
- name: Test RPM package lifecycle
run: su -s /bin/sh nobody -c 'prove -v xCAT-test/native/xcatd_rpm_lifecycle.t xCAT-test/native/apache_rpm_configuration.t'
@@ -0,0 +1,25 @@
Name: %{test_package}
Version: 2.0
Release: 1
%if "%{test_package}" == "xCATsn"
Epoch: 4
%endif
Summary: Legacy Apache configuration ownership fixture
License: EPL
BuildArch: noarch
%description
Ordinary payload and saved defaults from before configuration-file ownership.
%install
mkdir -p %{buildroot}/etc/httpd/conf.d %{buildroot}/etc/apache2/conf.d %{buildroot}/etc/xcat/conf.orig
printf '# recorded payload\n' > %{buildroot}/etc/httpd/conf.d/xcat.conf
printf '# recorded payload\n' > %{buildroot}/etc/apache2/conf.d/xcat.conf
printf '# prior Apache 2.4 default\n' > %{buildroot}/etc/xcat/conf.orig/xcat.conf.apach24
printf '# prior Apache 2.2 default\n' > %{buildroot}/etc/xcat/conf.orig/xcat.conf.apach22
%files
%defattr(-,root,root)
/etc/httpd/conf.d/xcat.conf
/etc/apache2/conf.d/xcat.conf
/etc/xcat/conf.orig
@@ -0,0 +1,70 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use Test::More;
use XCAT::Test::File qw(slurp_repo_file);
use XCAT::Test::RPM;
plan skip_all => 'Native RPM distribution and Linux namespaces are required'
unless $^O eq 'linux' && -f '/etc/redhat-release';
sub succeeds {
my ($label, @result) = @_;
is($result[0], 0, $label) or diag "$result[1]$result[2]";
unlike($result[2], qr/scriptlet failed|scriptlet failure/i, "$label completes scriptlets");
}
for my $package (qw(xCAT xCATsn)) {
my $old_rpm = XCAT::Test::RPM->build_fixture('legacy-apache', '--define', "test_package $package");
for my $target (
['el-modern', 'xcat.conf.apach24', '--define', 'rhel 8'],
['el-legacy', 'xcat.conf', '--define', 'rhel 6'],
['suse-modern', 'xcat.conf.apach24', '--define', 'rhel 0', '--define', 'suse_version 1500'],
) {
my ($label, $source, @defines) = @$target;
my $rpm = XCAT::Test::RPM->build($package, 1, @defines);
my $expected = slurp_repo_file("xCAT/$source");
for my $state (qw(default24 default22 edited absent symlink fresh)) {
subtest "$package $label $state configuration" => sub {
my $root = XCAT::Test::RPM->new;
$root->write('/opt/xcat/sbin/xcatconfig', "#!/bin/sh\nexit 0\n", 0755);
$root->write('/opt/xcat/share/xcat/scripts/xHRM', '');
succeeds('install legacy ownership fixture', $root->install($old_rpm)) unless $state eq 'fresh';
my $prior = $state eq 'default22' ? "# prior Apache 2.2 default\n" : "# prior Apache 2.4 default\n";
$root->write('/etc/xcat/conf.orig/xcat.conf.apach24', $prior) if $state eq 'fresh';
for my $daemon (qw(httpd apache2)) {
my $active = "/etc/$daemon/conf.d/xcat.conf";
if ($state eq 'absent' || $state eq 'symlink') {
unlink $root->path($active) or die "unlink $active: $!";
}
if ($state eq 'symlink') {
$root->write("/etc/$daemon/local.conf", $prior);
symlink('../local.conf', $root->path($active)) or die "symlink: $!";
} elsif ($state ne 'absent') {
$root->write($active, $state eq 'edited' ? "# administrator configuration\n" : $prior);
}
}
succeeds('upgrade to configuration ownership', $root->install($rpm));
for my $daemon (qw(httpd apache2)) {
my $active = "/etc/$daemon/conf.d/xcat.conf";
ok(-e $root->path($active), "$daemon has active configuration");
next unless -e $root->path($active);
if ($state eq 'edited' || $state eq 'symlink' || $state eq 'fresh') {
is($root->read($active), $state eq 'edited' ? "# administrator configuration\n" : $prior, "$daemon retains local bytes");
ok(-f $root->path("$active.rpmnew"), "$daemon receives a separate package default");
is($root->read("$active.rpmnew"), $expected, "$daemon rpmnew has selected Apache syntax") if -f $root->path("$active.rpmnew");
is(readlink($root->path($active)), '../local.conf', "$daemon keeps administrator symlink") if $state eq 'symlink';
} else {
is($root->read($active), $expected, "$daemon installs selected Apache syntax");
ok(!-e $root->path("$active.rpmnew"), "$daemon default is active without an rpmnew");
}
ok(!-e $root->path("$active.rpmsave"), "$daemon does not leave an rpmsave on upgrade");
}
};
}
}
}
done_testing();
@@ -1,152 +0,0 @@
#!/usr/bin/env perl
# xcat.conf used to be ordinary payload rewritten from a conf.orig template in
# %post, so an upgrade discarded local edits with no .rpmnew or .rpmsave. Pin
# the replacement: build-time source selection, config(noreplace) ownership,
# and a %pretrans migration that only removes a still-unmodified active file.
# The migration has to be in %pretrans: rpm fixes each config file's fate
# before %pre, so removing the file there can leave only a .rpmnew behind.
use strict;
use warnings;
use File::Spec;
use FindBin;
use Test::More;
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
sub read_file {
my ($relative_path) = @_;
my $path = File::Spec->catfile( $repo_root, split( '/', $relative_path ) );
open( my $fh, '<', $path ) or die "Unable to read $path: $!";
my $contents = do { local $/; <$fh> };
close($fh);
return $contents;
}
# Only real section keywords end a section, so nested %if/%ifos/%define stay put.
my %SECTION = map { $_ => 1 }
qw(description prep build pretrans pre install post preun postun posttrans clean files changelog);
sub section {
my ( $spec, $wanted ) = @_;
my ( $inside, @body ) = (0);
for my $line ( split( /\n/, $spec, -1 ) ) {
if ( $line =~ /^%(\w+)/ && $SECTION{$1} ) {
$inside = ( $1 eq $wanted );
next;
}
push @body, $line if $inside;
}
return join( "\n", @body ) . "\n";
}
my @packages = (
{
name => 'management-node',
spec => 'xCAT/xCAT.spec',
# %httpconfigdir redirects these to /etc/xcathttpdsave on s390x.
templates => '/etc/%httpconfigdir/conf.orig',
apache24 => 7,
order => [ 'httpd', 'apache2' ],
},
{
name => 'service-node',
spec => 'xCATsn/xCATsn.spec',
templates => '/etc/xcat/conf.orig',
apache24 => 6,
order => [ 'apache2', 'httpd' ],
},
);
for my $pkg (@packages) {
my $label = $pkg->{name};
my $spec = read_file( $pkg->{spec} );
my $templates = quotemeta $pkg->{templates};
my $apache24 = $pkg->{apache24};
my ( $first, $second ) = @{ $pkg->{order} };
# The %install selection is by source number, so pin what each one holds.
like( $spec, qr{^Source1:\s+xcat\.conf$}m,
"$label package still takes the Apache 2.2 configuration from Source1" );
like( $spec, qr{^Source$apache24:\s+xcat\.conf\.apach24$}m,
"$label package still takes the Apache 2.4 configuration from Source$apache24" );
my $install = section( $spec, 'install' );
# The next upgrade's %pretrans compares against these, so they must stay shipped.
like( $install, qr{^cp \s+ %\{SOURCE$apache24\} \s+ \$RPM_BUILD_ROOT$templates/xcat\.conf\.apach24 \s* $}mx,
"$label package still saves the Apache 2.4 template under conf.orig" );
like( $install, qr{^cp \s+ %\{SOURCE1\} \s+ \$RPM_BUILD_ROOT$templates/xcat\.conf\.apach22 \s* $}mx,
"$label package still saves the Apache 2.2 template under conf.orig" );
my $files = section( $spec, 'files' );
for my $dir ( 'httpd', 'apache2' ) {
like( $files, qr{^%config\(noreplace\)\s+/etc/$dir/conf\.d/xcat\.conf\s*$}m,
"$label package owns /etc/$dir/conf.d/xcat.conf as config(noreplace)" );
}
like( $files, qr{^$templates/xcat\.conf\.apach24\s*$}m,
"$label package ships the Apache 2.4 template" );
like( $files, qr{^$templates/xcat\.conf\.apach22\s*$}m,
"$label package ships the Apache 2.2 template" );
my $post = section( $spec, 'post' );
unlike( $post, qr{\brm\b[^\n]*conf\.d/xcat\.conf},
"$label %post never removes an active xcat.conf" );
unlike( $post, qr{\bcp\b[^\n]*xcat\.conf},
"$label %post never copies a template over an active xcat.conf" );
unlike( $post, qr{conf\.orig},
"$label %post no longer reads the saved templates at all" );
# The only mention left in %post is the read-only guard around a2enmod.
my @post_refs = ( $post =~ m{^([^\n]*conf\.d/xcat\.conf[^\n]*)$}mg );
is( scalar @post_refs, 1,
"$label %post refers to an active xcat.conf exactly once" );
like( $post_refs[0] || '',
qr{^\s*if \[ -e /etc/apache2/conf\.d/xcat\.conf \] && command -v a2enmod\b},
"$label %post only tests for the file, to enable mod_headers" );
# rpm decides each config file's fate before %pre, so the migration has to
# run in %pretrans or it can delete a file rpm already resolved to .rpmnew.
like( $spec, qr{^%pretrans -p <lua>$}m,
"$label package migrates in %pretrans, as an embedded Lua scriptlet" );
my $pre = section( $spec, 'pre' );
unlike( $pre, qr{conf\.d/xcat\.conf},
"$label %pre does not touch an active xcat.conf" );
my $pretrans = section( $spec, 'pretrans' );
like( $pretrans, qr{^if arg\[2\] and tonumber\(arg\[2\]\) == 1 then return end$}m,
"$label migration returns early on a fresh install" );
like(
$pretrans,
qr{
^local \s+ templates \s* = \s* \{ \s*
contents\("$templates/xcat\.conf\.apach24"\), \s* $ \n
^\s* contents\("$templates/xcat\.conf\.apach22"\) \s* \} \s* $
}mx,
"$label migration reads both of the outgoing package's saved templates"
);
like(
$pretrans,
qr{
^for \s+ _, \s* active \s+ in \s+ ipairs\(\{ \s* "/etc/httpd/conf\.d/xcat\.conf", \s* $ \n
^\s* "/etc/apache2/conf\.d/xcat\.conf" \s* \}\) \s+ do \s* $ \n
(?: ^\s* --[^\n]* \n )* # explanatory comment
^\s* if \s+ posix\.stat\(active, \s* "type"\) \s* == \s* "regular" \s+ then \s* $ \n
^\s* local \s+ current \s* = \s* contents\(active\) \s* $ \n
^\s* for \s+ _, \s* template \s+ in \s+ ipairs\(templates\) \s+ do \s* $ \n
^\s* if \s+ current \s+ and \s+ current \s* == \s* template \s+ then \s* $ \n
^\s* os\.remove\(active\) \s* $
}mx,
"$label migration removes an active xcat.conf only when it is a regular file whose contents still match a saved template"
);
# Nothing else may delete the active file, in any scriptlet.
unlike( $spec, qr{\brm\b[^\n]*conf\.d/xcat\.conf},
"$label package never shells out to rm for an active xcat.conf" );
my @removals = ( $pretrans =~ m{^\s*(os\.remove\([^\n]*)$}mg );
is_deeply( \@removals, ['os.remove(active)'],
"$label migration removes exactly one validated path and nothing else" );
unlike( $spec, qr{^Requires\(pre\):\s+/usr/bin/cmp$}m,
"$label package needs no external tool for the comparison" );
}
done_testing();