mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-09-26 01:34:05 +00:00
Merge pull request #7858 from VersatusHPC/test/r26-syncfiles-request-behavior
test(syncfiles): verify emitted xdcp requests
This commit is contained in:
@@ -3,62 +3,227 @@ use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use File::Spec;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use Storable qw(dclone);
|
||||
use Test::More;
|
||||
use XCAT::Test::File qw(repo_path);
|
||||
|
||||
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
|
||||
our $RCP;
|
||||
|
||||
sub slurp {
|
||||
my ($rel) = @_;
|
||||
my $path = File::Spec->catfile( $repo_root, $rel );
|
||||
return unless -r $path;
|
||||
open( my $fh, '<', $path ) or die "Unable to read $path: $!";
|
||||
my $c = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
return $c;
|
||||
BEGIN {
|
||||
package xCAT::Utils;
|
||||
$INC{'xCAT/Utils.pm'} = __FILE__;
|
||||
|
||||
package xCAT::Postage;
|
||||
$INC{'xCAT/Postage.pm'} = __FILE__;
|
||||
|
||||
package xCAT::SvrUtils;
|
||||
our $synclist;
|
||||
sub getsynclistfile {
|
||||
my ( $class, $nodes ) = @_;
|
||||
return unless defined $synclist;
|
||||
return { map { $_ => $synclist->{$_} } @$nodes };
|
||||
}
|
||||
$INC{'xCAT/SvrUtils.pm'} = __FILE__;
|
||||
|
||||
package xCAT::MsgUtils;
|
||||
our @messages;
|
||||
sub message {
|
||||
my ( $class, @message ) = @_;
|
||||
push @messages, \@message;
|
||||
return;
|
||||
}
|
||||
$INC{'xCAT/MsgUtils.pm'} = __FILE__;
|
||||
|
||||
package xCAT::NodeRange;
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw(noderange);
|
||||
our %nodes;
|
||||
sub noderange { return $nodes{ $_[0] }; }
|
||||
$INC{'xCAT/NodeRange.pm'} = __FILE__;
|
||||
}
|
||||
|
||||
my $syncfiles = slurp('xCAT-server/lib/xcat/plugins/syncfiles.pm');
|
||||
my $updatenode = slurp('xCAT-server/lib/xcat/plugins/updatenode.pm');
|
||||
my $xdsh = slurp('xCAT-server/lib/xcat/plugins/xdsh.pm');
|
||||
my $plugin = repo_path('xCAT-server/lib/xcat/plugins/syncfiles.pm');
|
||||
require $plugin;
|
||||
|
||||
plan skip_all => 'plugins not found'
|
||||
unless defined($syncfiles) && defined($updatenode) && defined($xdsh);
|
||||
sub run_syncfiles {
|
||||
my ($case) = @_;
|
||||
local %xCAT::NodeRange::nodes = (
|
||||
'node1.example.test' => 'node1',
|
||||
'node2.example.test' => 'node2',
|
||||
);
|
||||
local $xCAT::SvrUtils::synclist = $case->{synclist};
|
||||
local @xCAT::MsgUtils::messages;
|
||||
local @ARGV;
|
||||
my @sent;
|
||||
my $callback = sub { return; };
|
||||
my $request = dclone($case->{request} || {
|
||||
command => ['syncfiles'],
|
||||
username => ['operator'],
|
||||
arg => $case->{args},
|
||||
_xcat_clienthost => [ $case->{client} ],
|
||||
});
|
||||
my $original = dclone($request);
|
||||
my @warnings;
|
||||
|
||||
# The xdcp subrequest has to state the identity the sync runs as.
|
||||
my ($call) = $syncfiles =~ /(\$subreq->\(\{[^}]*command\s*=>\s*\['xdcp'\][^}]*\})/s;
|
||||
ok( $call, 'the xdcp subrequest was located in syncfiles' )
|
||||
or BAIL_OUT('syncfiles.pm no longer matches the expected subrequest shape');
|
||||
{
|
||||
local $SIG{__WARN__} = sub { push @warnings, @_ } if $case->{warnings};
|
||||
xCAT_plugin::syncfiles::process_request(
|
||||
$request,
|
||||
$callback,
|
||||
sub {
|
||||
my ( $outgoing, $response_callback ) = @_;
|
||||
push @sent, [ dclone($outgoing), $response_callback ];
|
||||
return;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
like( $call, qr/username\s*=>/, 'the xdcp subrequest names a username' );
|
||||
is_deeply( \@warnings, $case->{warnings}, 'the option diagnostics match' ) if $case->{warnings};
|
||||
is_deeply( $request, $original, 'the caller request is unchanged' );
|
||||
is( scalar @sent, scalar @{ $case->{expected} }, 'the request count matches' );
|
||||
for my $index ( 0 .. $#{ $case->{expected} } ) {
|
||||
my ( $node, $file, $copy_args ) = @{ $case->{expected}->[$index] };
|
||||
my $sent = $sent[$index] || [];
|
||||
is_deeply(
|
||||
$sent->[0],
|
||||
{
|
||||
command => ['xdcp'],
|
||||
username => ['root'],
|
||||
node => [$node],
|
||||
arg => [ '-F', $file, @$copy_args ],
|
||||
env => ["DSH_RSYNC_FILE=$file"],
|
||||
},
|
||||
"request $index carries the root identity and copy parameters",
|
||||
);
|
||||
is( $sent->[1], $callback, "request $index retains the response callback" );
|
||||
}
|
||||
is( scalar @xCAT::MsgUtils::messages, scalar @{ $case->{messages} }, 'the diagnostic count matches' );
|
||||
for my $index ( 0 .. $#{ $case->{messages} } ) {
|
||||
my $message = $xCAT::MsgUtils::messages[$index] || [];
|
||||
is( $message->[0], 'S', 'the diagnostic goes to the system log' );
|
||||
like( $message->[1], $case->{messages}->[$index], 'the diagnostic identifies the failure' );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
# It must be an array reference. A bare string was the original form of this
|
||||
# change and broke the non-hierarchical path, because the consumers index it as
|
||||
# $request->{username}->[0].
|
||||
like(
|
||||
$call,
|
||||
qr/username\s*=>\s*\['root'\]/,
|
||||
'the username is the arrayref form the consumers index into'
|
||||
);
|
||||
unlike(
|
||||
$call,
|
||||
qr/username\s*=>\s*'root'\s*,/,
|
||||
'the username is not a bare string, which would not survive ->[0]'
|
||||
my @cases = (
|
||||
{
|
||||
name => 'daemon request without arguments or username',
|
||||
request => { command => ['syncfiles'], _xcat_clienthost => ['node1.example.test'] },
|
||||
synclist => { node1 => '/install/custom/sync-a' },
|
||||
expected => [ [ 'node1', '/install/custom/sync-a', [] ] ],
|
||||
messages => [],
|
||||
},
|
||||
{
|
||||
name => 'multiple sync files retain order and identity',
|
||||
client => 'node2.example.test',
|
||||
args => [],
|
||||
synclist => { node2 => '/install/custom/sync-b,/install/custom/sync-a,/install/custom/sync-c' },
|
||||
expected => [
|
||||
[ 'node2', '/install/custom/sync-b', [] ],
|
||||
[ 'node2', '/install/custom/sync-a', [] ],
|
||||
[ 'node2', '/install/custom/sync-c', [] ],
|
||||
],
|
||||
messages => [],
|
||||
},
|
||||
);
|
||||
|
||||
# The consumers this has to satisfy, pinned so the shape cannot drift apart.
|
||||
like(
|
||||
$xdsh,
|
||||
qr/\$ENV\{DSH_FROM_USERID\}\s*=\s*\$request->\{username\}->\[0\]/,
|
||||
'xdsh still derives DSH_FROM_USERID from the request username'
|
||||
);
|
||||
for my $option ( '-r', '-c', '--node-rcp' ) {
|
||||
push @cases, {
|
||||
name => "copy override $option retains identity on every request",
|
||||
client => 'node1.example.test',
|
||||
args => [ $option, '/usr/bin/scp' ],
|
||||
synclist => { node1 => '/install/custom/sync-a,/install/custom/sync-b' },
|
||||
expected => [
|
||||
[ 'node1', '/install/custom/sync-a', [ '-r', '/usr/bin/scp' ] ],
|
||||
[ 'node1', '/install/custom/sync-b', [ '-r', '/usr/bin/scp' ] ],
|
||||
],
|
||||
messages => [],
|
||||
};
|
||||
}
|
||||
|
||||
# updatenode makes the same xdcp call and already passes a username. The two
|
||||
# should not diverge again.
|
||||
like(
|
||||
$updatenode,
|
||||
qr/command\s*=>\s*\["xdcp"\][^;]*username\s*=>/s,
|
||||
'updatenode still passes a username on its own xdcp call'
|
||||
);
|
||||
push @cases,
|
||||
{
|
||||
name => 'one sync file',
|
||||
client => 'node1.example.test',
|
||||
args => [],
|
||||
synclist => { node1 => '/install/custom/sync-a' },
|
||||
expected => [ [ 'node1', '/install/custom/sync-a', [] ] ],
|
||||
messages => [],
|
||||
},
|
||||
{
|
||||
name => 'invalid option sends no request',
|
||||
client => 'node1.example.test',
|
||||
args => ['--bogus'],
|
||||
synclist => { node1 => '/install/custom/sync-a' },
|
||||
expected => [],
|
||||
messages => [ qr/Received syncfiles from node1\.example\.test, with invalid options\b/ ],
|
||||
warnings => ["Unknown option: bogus\n"],
|
||||
},
|
||||
{
|
||||
name => 'unavailable synclist lookup sends no request',
|
||||
client => 'node1.example.test',
|
||||
args => [],
|
||||
synclist => undef,
|
||||
expected => [],
|
||||
messages => [ qr/\ACannot find synclist file for the node1\z/ ],
|
||||
},
|
||||
{
|
||||
name => 'node without a synclist sends no request',
|
||||
client => 'node1.example.test',
|
||||
args => [],
|
||||
synclist => { node1 => undef },
|
||||
expected => [],
|
||||
messages => [],
|
||||
},
|
||||
{
|
||||
name => 'unresolved client sends no request',
|
||||
client => 'unknown.example.test',
|
||||
args => [],
|
||||
synclist => { node1 => '/install/custom/sync-a' },
|
||||
expected => [],
|
||||
messages => [ qr/couldn't be correlated to a node/ ],
|
||||
};
|
||||
|
||||
for my $case (@cases) {
|
||||
subtest $case->{name} => sub {
|
||||
local $RCP;
|
||||
run_syncfiles($case);
|
||||
};
|
||||
}
|
||||
|
||||
# Direct same-process calls share the existing override; daemon process isolation is outside this test.
|
||||
for my $option ( '-r', '-c', '--node-rcp' ) {
|
||||
subtest "same-process copy override $option" => sub {
|
||||
local $RCP;
|
||||
for my $step (
|
||||
[ 'clean sequence state', [], 'node1', 'sync-default', undef ],
|
||||
[ 'initial override', [ $option, '/usr/bin/scp' ], 'node1', 'sync-a', '/usr/bin/scp' ],
|
||||
[ 'rejected override', [ $option, '/usr/bin/false', '--bogus' ], 'node1', 'sync-rejected', undef, 1 ],
|
||||
[ 'request without override', [], 'node2', 'sync-b', '/usr/bin/scp' ],
|
||||
[ 'replacement override', [ $option, '/usr/bin/rsync' ], 'node1', 'sync-c', '/usr/bin/rsync' ],
|
||||
[ 'request after replacement', [], 'node2', 'sync-d', '/usr/bin/rsync' ],
|
||||
)
|
||||
{
|
||||
my ( $name, $args, $node, $list, $copy_command, $invalid ) = @$step;
|
||||
subtest $name => sub {
|
||||
my $file = "/install/custom/$list";
|
||||
my $copy_args = defined $copy_command ? [ '-r', $copy_command ] : [];
|
||||
my %diagnostics = $invalid ? (
|
||||
messages => [ qr/Received syncfiles from \Q$node.example.test\E, with invalid options\b/ ],
|
||||
warnings => ["Unknown option: bogus\n"],
|
||||
) : ( messages => [] );
|
||||
run_syncfiles({
|
||||
client => "$node.example.test",
|
||||
args => $args,
|
||||
synclist => { $node => $file },
|
||||
expected => $invalid ? [] : [ [ $node, $file, $copy_args ] ],
|
||||
%diagnostics,
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
done_testing();
|
||||
|
||||
Reference in New Issue
Block a user