diff --git a/.github/workflows/xcat_test.yml b/.github/workflows/xcat_test.yml index 4efa9aa52..074cce85d 100644 --- a/.github/workflows/xcat_test.yml +++ b/.github/workflows/xcat_test.yml @@ -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' diff --git a/xCAT-test/fixtures/package-lifecycle/legacy-apache.spec b/xCAT-test/fixtures/package-lifecycle/legacy-apache.spec new file mode 100644 index 000000000..88d9a0e3d --- /dev/null +++ b/xCAT-test/fixtures/package-lifecycle/legacy-apache.spec @@ -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 diff --git a/xCAT-test/native/apache_rpm_configuration.t b/xCAT-test/native/apache_rpm_configuration.t new file mode 100644 index 000000000..8e91e193f --- /dev/null +++ b/xCAT-test/native/apache_rpm_configuration.t @@ -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(); diff --git a/xCAT-test/unit/xcat_conf_upgrade_preservation.t b/xCAT-test/unit/xcat_conf_upgrade_preservation.t deleted file mode 100644 index 73f2a2c06..000000000 --- a/xCAT-test/unit/xcat_conf_upgrade_preservation.t +++ /dev/null @@ -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 $}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();