mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-08-26 00:26:40 +00:00
test(genesis): cover provisioning runtime
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use Test::More;
|
||||
|
||||
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
|
||||
my $action_script = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-action)
|
||||
);
|
||||
my $status_script = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-status)
|
||||
);
|
||||
my $functions_file = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-functions)
|
||||
);
|
||||
my $action_service = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files xcat-genesis-action.service)
|
||||
);
|
||||
my $init_recipe = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init xcat-genesis-init_1.0.bb)
|
||||
);
|
||||
|
||||
sub write_file {
|
||||
my ( $path, $contents, $mode ) = @_;
|
||||
open( my $fh, '>', $path ) or die "Unable to write $path: $!";
|
||||
print {$fh} $contents;
|
||||
close($fh);
|
||||
chmod( $mode, $path ) if defined($mode);
|
||||
}
|
||||
|
||||
sub read_file {
|
||||
my ($path) = @_;
|
||||
open( my $fh, '<', $path ) or die "Unable to read $path: $!";
|
||||
my $contents = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
return $contents;
|
||||
}
|
||||
|
||||
my $root = tempdir( CLEANUP => 1 );
|
||||
my $bin = File::Spec->catdir( $root, 'bin' );
|
||||
my $state_dir = File::Spec->catdir( $root, 'run' );
|
||||
my $status_dir = File::Spec->catdir( $state_dir, 'status' );
|
||||
my $approved_dir = File::Spec->catdir( $root, 'actions' );
|
||||
my $destiny_file = File::Spec->catfile( $state_dir, 'destiny' );
|
||||
my $metadata_file = File::Spec->catfile( $state_dir, 'xcat-response.env' );
|
||||
my $network_file = File::Spec->catfile( $state_dir, 'genesis.env' );
|
||||
my $certificate_file = File::Spec->catfile( $root, 'cert.pem' );
|
||||
my $uptime_file = File::Spec->catfile( $root, 'uptime' );
|
||||
my $command_log = File::Spec->catfile( $root, 'commands.log' );
|
||||
my $getdestiny_queue = File::Spec->catfile( $root, 'getdestiny.queue' );
|
||||
my $nextdestiny_queue = File::Spec->catfile( $root, 'nextdestiny.queue' );
|
||||
|
||||
make_path( $bin, $state_dir, $status_dir, $approved_dir );
|
||||
write_file( $network_file, <<'ENV' );
|
||||
XCATDEST=192.0.2.10:3001
|
||||
XCATMASTER=192.0.2.10
|
||||
XCATPORT=3001
|
||||
XCAT_INTERFACE=eth0
|
||||
XCAT_SOURCE_ADDRESS=192.0.2.98
|
||||
ENV
|
||||
write_file( $uptime_file, "42.00 80.00\n" );
|
||||
|
||||
write_file( File::Spec->catfile( $bin, 'logger' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf 'logger %s\n' "$*" >>"$XCAT_TEST_LOG"
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'getdestiny' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf 'getdestiny %s\n' "$*" >>"$XCAT_TEST_LOG"
|
||||
metadata=
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--metadata) metadata=$2; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
[ -s "$XCAT_TEST_GETDESTINY_QUEUE" ] || exit 1
|
||||
response=$(head -n 1 "$XCAT_TEST_GETDESTINY_QUEUE")
|
||||
tail -n +2 "$XCAT_TEST_GETDESTINY_QUEUE" >"$XCAT_TEST_GETDESTINY_QUEUE.new"
|
||||
mv "$XCAT_TEST_GETDESTINY_QUEUE.new" "$XCAT_TEST_GETDESTINY_QUEUE"
|
||||
if [ -n "$metadata" ]; then
|
||||
printf '%s\n' 'XCAT_NODE_NAME=node042' "XCAT_DESTINY=$response" >"$metadata"
|
||||
fi
|
||||
printf '%s\n' "$response"
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'nextdestiny' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf 'nextdestiny %s\n' "$*" >>"$XCAT_TEST_LOG"
|
||||
[ -s "$XCAT_TEST_NEXTDESTINY_QUEUE" ] || exit 1
|
||||
response=$(head -n 1 "$XCAT_TEST_NEXTDESTINY_QUEUE")
|
||||
tail -n +2 "$XCAT_TEST_NEXTDESTINY_QUEUE" >"$XCAT_TEST_NEXTDESTINY_QUEUE.new"
|
||||
mv "$XCAT_TEST_NEXTDESTINY_QUEUE.new" "$XCAT_TEST_NEXTDESTINY_QUEUE"
|
||||
printf '%s\n' "$response"
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'discover' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf '%s\n' discover >>"$XCAT_TEST_LOG"
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'getcert' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf '%s\n' getcert >>"$XCAT_TEST_LOG"
|
||||
: >"$XCAT_TEST_CERTIFICATE_FILE"
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'reboot-control' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf 'reboot-control %s\n' "$*" >>"$XCAT_TEST_LOG"
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'poweroff-control' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf 'poweroff-control %s\n' "$*" >>"$XCAT_TEST_LOG"
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'ipmitool' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf 'ipmitool %s\n' "$*" >>"$XCAT_TEST_LOG"
|
||||
[ "${XCAT_TEST_IPMI-0}" = 1 ]
|
||||
SH
|
||||
write_file( File::Spec->catfile( $approved_dir, 'inventory' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf 'inventory' >>"$XCAT_TEST_LOG"
|
||||
printf ' <%s>' "$@" >>"$XCAT_TEST_LOG"
|
||||
printf '\n' >>"$XCAT_TEST_LOG"
|
||||
SH
|
||||
|
||||
my %base_environment = (
|
||||
PATH => "$bin:$ENV{PATH}",
|
||||
XCAT_STATE_DIR => $state_dir,
|
||||
XCAT_STATUS_COMMAND => $status_script,
|
||||
XCAT_STATUS_DIR => $status_dir,
|
||||
XCAT_DESTINY_FILE => $destiny_file,
|
||||
XCAT_METADATA_FILE => $metadata_file,
|
||||
XCAT_NETWORK_FILE => $network_file,
|
||||
XCAT_DISCOVER_COMMAND => File::Spec->catfile( $bin, 'discover' ),
|
||||
XCAT_CERTIFICATE_COMMAND => File::Spec->catfile( $bin, 'getcert' ),
|
||||
XCAT_CERTIFICATE_FILE => $certificate_file,
|
||||
XCAT_GETDESTINY_COMMAND => File::Spec->catfile( $bin, 'getdestiny' ),
|
||||
XCAT_NEXTDESTINY_COMMAND => File::Spec->catfile( $bin, 'nextdestiny' ),
|
||||
XCAT_ACTION_COMMAND_DIR => $approved_dir,
|
||||
XCAT_REBOOT_COMMAND => File::Spec->catfile( $bin, 'reboot-control' ),
|
||||
XCAT_POWEROFF_COMMAND => File::Spec->catfile( $bin, 'poweroff-control' ),
|
||||
XCAT_ACTION_POLL_SECONDS => 0,
|
||||
XCAT_ACTION_REQUEST_TIMEOUT => 2,
|
||||
XCAT_ACTION_OPERATION_TIMEOUT => 2,
|
||||
XCAT_ACTION_MAX_STEPS => 1,
|
||||
XCAT_UPTIME_FILE => $uptime_file,
|
||||
XCAT_GENESIS_FUNCTIONS => $functions_file,
|
||||
XCAT_TEST_LOG => $command_log,
|
||||
XCAT_TEST_GETDESTINY_QUEUE => $getdestiny_queue,
|
||||
XCAT_TEST_NEXTDESTINY_QUEUE => $nextdestiny_queue,
|
||||
XCAT_TEST_CERTIFICATE_FILE => $certificate_file,
|
||||
);
|
||||
|
||||
sub run_action {
|
||||
my ( $action, %options ) = @_;
|
||||
|
||||
unlink( $command_log, $certificate_file );
|
||||
unlink( File::Spec->catfile( $status_dir, 'action.env' ) );
|
||||
write_file( $destiny_file, "$action\n" );
|
||||
write_file( $getdestiny_queue,
|
||||
$options{getdestiny} // "$action\n" );
|
||||
write_file( $nextdestiny_queue,
|
||||
$options{nextdestiny} // "standby\n" );
|
||||
write_file( $certificate_file, "certificate\n" )
|
||||
unless $options{without_certificate};
|
||||
local %ENV = ( %ENV, %base_environment,
|
||||
XCAT_ACTION_MAX_STEPS => ( $options{maximum_steps} // 1 ),
|
||||
XCAT_TEST_IPMI => ( $options{ipmi} // 0 ) );
|
||||
my $status = system( '/bin/bash', $action_script ) >> 8;
|
||||
my $log = -r $command_log ? read_file($command_log) : '';
|
||||
my $action_status = File::Spec->catfile( $status_dir, 'action.env' );
|
||||
my $record = -r $action_status ? read_file($action_status) : '';
|
||||
return ( $status, $log, $record );
|
||||
}
|
||||
|
||||
my ( $status, $log, $record ) = run_action(
|
||||
'discover', getdestiny => "standby\n" );
|
||||
is( $status, 0, 'discovery action completes' );
|
||||
like( $log, qr/^discover$/m, 'discovery action sends inventory' );
|
||||
like( $log, qr/^getcert$/m, 'discovery action enrolls a certificate' );
|
||||
ok( index( $log, "discover\n" ) < index( $log, 'getdestiny ' ),
|
||||
'registered discovery runs before the next xCAT query' );
|
||||
like( read_file($destiny_file), qr/^standby$/,
|
||||
'discovery loads the assigned node action' );
|
||||
|
||||
( $status, $log, $record ) = run_action(
|
||||
'shell', getdestiny => "install test-image\n" );
|
||||
is( $status, 0, 'shell action becomes a managed wait state' );
|
||||
unlike( $log, qr{(?:^|/)bash(?:\s|$)}, 'shell action does not open a local shell' );
|
||||
like( read_file($destiny_file), qr/^install test-image$/,
|
||||
'wait state accepts a remote action change' );
|
||||
|
||||
( $status, $log, $record ) = run_action(
|
||||
'standby', getdestiny => "standby\nstandby\n",
|
||||
without_certificate => 1, maximum_steps => 2 );
|
||||
is( $status, 0, 'standby action polls xCAT' );
|
||||
like( $log, qr/^getcert$/m, 'known nodes obtain a missing certificate' );
|
||||
like( $record, qr/^STATE=IDLE$/m, 'standby publishes an idle state' );
|
||||
like( $record, qr/^VERIFIED_SECONDS=42$/m,
|
||||
'standby records its latest successful xCAT contact' );
|
||||
|
||||
for my $action (qw(osimage ondiscover)) {
|
||||
( $status, $log, $record ) = run_action($action);
|
||||
is( $status, 0, "$action action completes" );
|
||||
like( $log, qr/^nextdestiny /m, "$action advances the action chain" );
|
||||
}
|
||||
|
||||
( $status, $log, $record ) = run_action(
|
||||
'runcmd=inventory storage safe;reboot' );
|
||||
is( $status, 0, 'approved command completes' );
|
||||
like( $log, qr/^inventory <storage> <safe;reboot>$/m,
|
||||
'approved command arguments are not evaluated by a shell' );
|
||||
like( $log, qr/^nextdestiny /m, 'approved command advances the chain' );
|
||||
|
||||
( $status, $log, $record ) = run_action('runcmd=unpackaged');
|
||||
isnt( $status, 0, 'unpackaged command is rejected' );
|
||||
like( $record, qr/^CODE=ACTION_COMMAND_NOT_APPROVED$/m,
|
||||
'unpackaged command has a specific failure code' );
|
||||
|
||||
for my $case (
|
||||
[ runimage => 'UNSAFE_LEGACY_ACTION' ],
|
||||
[ configraid => 'LEGACY_STORAGE_ACTION' ],
|
||||
[ sysclone => 'LEGACY_SYSCLONE_ACTION' ],
|
||||
) {
|
||||
( $status, $log, $record ) = run_action( $case->[0] );
|
||||
isnt( $status, 0, "$case->[0] fails closed" );
|
||||
like( $record, qr/^CODE=\Q$case->[1]\E$/m,
|
||||
"$case->[0] reports its migration status" );
|
||||
}
|
||||
|
||||
for my $action (qw(boot reboot)) {
|
||||
( $status, $log, $record ) = run_action( $action, ipmi => 1 );
|
||||
is( $status, 0, "$action action completes" );
|
||||
like( $log, qr/^nextdestiny /m, "$action advances the chain" );
|
||||
like( $log, qr/^ipmitool chassis bootdev pxe$/m,
|
||||
"$action requests a one-time network boot" );
|
||||
like( $log, qr/^reboot-control reboot$/m, "$action requests a reboot" );
|
||||
}
|
||||
|
||||
for my $action (qw(install netboot statelite)) {
|
||||
( $status, $log, $record ) = run_action($action);
|
||||
is( $status, 0, "$action action completes" );
|
||||
unlike( $log, qr/^nextdestiny /m,
|
||||
"$action preserves the server-selected chain position" );
|
||||
like( $log, qr/^reboot-control reboot$/m, "$action requests a reboot" );
|
||||
}
|
||||
|
||||
( $status, $log, $record ) = run_action('shutdown');
|
||||
is( $status, 0, 'shutdown action completes' );
|
||||
like( $log, qr/^poweroff-control poweroff$/m,
|
||||
'shutdown action requests a poweroff' );
|
||||
|
||||
( $status, $log, $record ) = run_action('error=policy denied');
|
||||
isnt( $status, 0, 'xCAT error action fails' );
|
||||
like( $record, qr/^CODE=XCAT_ACTION_ERROR$/m,
|
||||
'xCAT error remains visible' );
|
||||
|
||||
( $status, $log, $record ) = run_action('unknown');
|
||||
isnt( $status, 0, 'unknown action fails' );
|
||||
like( $record, qr/^CODE=XCAT_ACTION_UNSUPPORTED$/m,
|
||||
'unknown action has a specific failure code' );
|
||||
|
||||
( $status, $log, $record ) = run_action(
|
||||
'standby', getdestiny => '', without_certificate => 0 );
|
||||
is( $status, 0, 'a failed refresh uses the previously received action' );
|
||||
like( $record, qr/^STATE=DEGRADED$/m,
|
||||
'a failed action poll remains visible' );
|
||||
|
||||
my $service = read_file($action_service);
|
||||
like( $service, qr/^Requires=xcat-genesis-register\.service$/m,
|
||||
'action execution requires registration' );
|
||||
like( $service, qr/^Restart=on-failure$/m,
|
||||
'failed actions retry after operator correction' );
|
||||
like( $service, qr/^StartLimitIntervalSec=0$/m,
|
||||
'action recovery is not disabled by a start limit' );
|
||||
|
||||
my $recipe = read_file($init_recipe);
|
||||
like( $recipe, qr/\bxcat-genesis-discovery\b/,
|
||||
'action runtime includes discovery support' );
|
||||
like( $recipe, qr/file:\/\/genesis-action/,
|
||||
'action executor is packaged' );
|
||||
like( $recipe, qr/file:\/\/genesis-network-refresh/,
|
||||
'discovery network refresh is packaged' );
|
||||
like( $recipe, qr/\bxcat-genesis-action\.service\b/,
|
||||
'action service is enabled' );
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,501 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use IO::Select;
|
||||
use IPC::Open3;
|
||||
use Symbol qw(gensym);
|
||||
use Test::More;
|
||||
|
||||
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
|
||||
my $discovery_dir = File::Spec->catdir(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-xcat xcat-genesis-discovery)
|
||||
);
|
||||
my $discover_script = File::Spec->catfile(
|
||||
$discovery_dir, qw(files genesis-discover)
|
||||
);
|
||||
my $callback_script = File::Spec->catfile(
|
||||
$discovery_dir, qw(files genesis-discovery-callback)
|
||||
);
|
||||
my $getcert_script = File::Spec->catfile(
|
||||
$discovery_dir, qw(files genesis-getcert)
|
||||
);
|
||||
my $credential_callback_script = File::Spec->catfile(
|
||||
$discovery_dir, qw(files genesis-credential-callback)
|
||||
);
|
||||
my $udp_sender_source = File::Spec->catfile(
|
||||
$discovery_dir, qw(files genesis-udp-send.c)
|
||||
);
|
||||
my $status_script = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-status)
|
||||
);
|
||||
|
||||
sub write_file {
|
||||
my ( $path, $contents, $mode ) = @_;
|
||||
open( my $fh, '>', $path ) or die "Unable to write $path: $!";
|
||||
print {$fh} $contents;
|
||||
close($fh);
|
||||
chmod( $mode, $path ) if defined($mode);
|
||||
}
|
||||
|
||||
sub read_file {
|
||||
my ($path) = @_;
|
||||
open( my $fh, '<', $path ) or die "Unable to read $path: $!";
|
||||
my $contents = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
return $contents;
|
||||
}
|
||||
|
||||
sub run_script {
|
||||
my ( $script, $environment, $input ) = @_;
|
||||
local %ENV = ( %ENV, %{$environment} );
|
||||
if ( defined($input) ) {
|
||||
open( my $pipe, '|-', '/bin/bash', $script )
|
||||
or die "Unable to run $script: $!";
|
||||
print {$pipe} $input;
|
||||
close($pipe);
|
||||
return $? >> 8;
|
||||
}
|
||||
return system( '/bin/bash', $script ) >> 8;
|
||||
}
|
||||
|
||||
sub read_callback_before_eof {
|
||||
my ( $script, $environment, $input ) = @_;
|
||||
local %ENV = ( %ENV, %{$environment} );
|
||||
my ( $child_in, $child_out );
|
||||
my $child_err = gensym;
|
||||
my $pid = open3( $child_in, $child_out, $child_err,
|
||||
'/bin/bash', $script );
|
||||
print {$child_in} $input;
|
||||
my $ready = IO::Select->new($child_out)->can_read(2);
|
||||
my $response = $ready ? <$child_out> : undef;
|
||||
close($child_in);
|
||||
waitpid( $pid, 0 );
|
||||
return $response;
|
||||
}
|
||||
|
||||
my $root = tempdir( CLEANUP => 1 );
|
||||
my $bin = File::Spec->catdir( $root, 'bin' );
|
||||
my $state_dir = File::Spec->catdir( $root, 'run' );
|
||||
my $key_dir = File::Spec->catdir( $root, 'keys' );
|
||||
my $proc_root = File::Spec->catdir( $root, 'proc' );
|
||||
my $dmi_dir = File::Spec->catdir(
|
||||
$root, qw(sys devices virtual dmi id)
|
||||
);
|
||||
my $eth0 = File::Spec->catdir( $root, qw(sys class net eth0) );
|
||||
my $driver = File::Spec->catdir( $root, qw(drivers virtio_net) );
|
||||
my $network_file = File::Spec->catfile( $state_dir, 'genesis.env' );
|
||||
my $response_file = File::Spec->catfile( $state_dir, 'discovery-response' );
|
||||
my $packet_file = File::Spec->catfile( $root, 'packet.xml' );
|
||||
my $credential_request = File::Spec->catfile( $root, 'credential-request.xml' );
|
||||
my $credential_response = File::Spec->catfile( $root, 'credential-response.xml' );
|
||||
my $metadata_file = File::Spec->catfile( $state_dir, 'xcat-response.env' );
|
||||
my $command_log = File::Spec->catfile( $root, 'commands.log' );
|
||||
my $uptime = File::Spec->catfile( $root, 'uptime' );
|
||||
|
||||
make_path( $bin, $state_dir, $key_dir, $proc_root, $dmi_dir,
|
||||
File::Spec->catdir( $eth0, 'device' ), $driver,
|
||||
File::Spec->catdir( $root, 'dev' ) );
|
||||
symlink( $driver, File::Spec->catfile( $eth0, 'device', 'driver' ) )
|
||||
or die "Unable to create driver link: $!";
|
||||
write_file( File::Spec->catfile( $eth0, 'address' ),
|
||||
"52:54:00:00:00:02\n" );
|
||||
write_file( File::Spec->catfile( $eth0, 'device', 'uevent' ),
|
||||
"PCI_SLOT_NAME=0000:00:03.0\n" );
|
||||
write_file( File::Spec->catfile( $eth0, 'device', 'physical_slot' ),
|
||||
"Slot 4\n" );
|
||||
write_file( File::Spec->catfile( $root, 'dev', 'ipmi0' ), '' );
|
||||
write_file( File::Spec->catfile( $dmi_dir, 'sys_vendor' ),
|
||||
"Acme & Co\n" );
|
||||
write_file( File::Spec->catfile( $dmi_dir, 'product_name' ),
|
||||
"Rack <Node>\n" );
|
||||
write_file( File::Spec->catfile( $dmi_dir, 'product_serial' ),
|
||||
"SN-0042\n" );
|
||||
write_file( File::Spec->catfile( $dmi_dir, 'product_uuid' ),
|
||||
"00112233-4455-6677-8899-aabbccddeeff\n" );
|
||||
write_file( File::Spec->catfile( $proc_root, 'cpuinfo' ), <<'CPU' );
|
||||
processor : 0
|
||||
model name : Test CPU & Controller
|
||||
processor : 1
|
||||
CPU
|
||||
write_file( File::Spec->catfile( $proc_root, 'meminfo' ),
|
||||
"MemTotal: 2097152 kB\n" );
|
||||
write_file( $uptime, "42.00 80.00\n" );
|
||||
write_file( $network_file, <<'ENV' );
|
||||
XCATDEST=192.0.2.10:3001
|
||||
XCATMASTER=192.0.2.10
|
||||
XCATPORT=3001
|
||||
XCAT_INTERFACE=eth0
|
||||
XCAT_SOURCE_ADDRESS=192.0.2.98
|
||||
ENV
|
||||
|
||||
write_file( File::Spec->catfile( $bin, 'logger' ),
|
||||
"#!/bin/sh\nexit 0\n", 0755 );
|
||||
write_file( File::Spec->catfile( $bin, 'uname' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
[ "$1" = "-m" ] && printf '%s\n' "${XCAT_TEST_ARCH-x86_64}"
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'ipmitool' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
case "$*" in
|
||||
'mc info') exit 0 ;;
|
||||
'sol info') printf '%s\n' 'Payload Channel : 1' ;;
|
||||
'lan print 1'|'lan print')
|
||||
printf '%s\n' 'IP Address Source : Static Address' \
|
||||
'IP Address : 192.0.2.101' \
|
||||
'MAC Address : 52:54:00:aa:bb:cc'
|
||||
;;
|
||||
esac
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'lldpcli' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf '%s\n' 'lldp.eth0.chassis.name=switch01' \
|
||||
'lldp.eth0.chassis.mgmt-ip=192.0.2.2' \
|
||||
'lldp.eth0.chassis.descr=Test switch' \
|
||||
'lldp.eth0.port.descr=Ethernet1/4'
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'lsblk' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf '%s\n' 'vda 21474836480 disk' 'vda1 1073741824 part'
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'ip' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
case "$*" in
|
||||
'-4 -o address show dev eth0 scope global')
|
||||
[ -z "$XCAT_TEST_IPV4" ] \
|
||||
|| printf '2: eth0 inet %s scope global eth0\n' "$XCAT_TEST_IPV4"
|
||||
;;
|
||||
'-6 -o address show dev eth0 scope global')
|
||||
[ -z "$XCAT_TEST_IPV6" ] \
|
||||
|| printf '2: eth0 inet6 %s scope global eth0\n' "$XCAT_TEST_IPV6"
|
||||
;;
|
||||
esac
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'openssl' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf 'openssl %s\n' "$*" >>"$XCAT_TEST_LOG"
|
||||
case "$1" in
|
||||
genpkey)
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = '-out' ]; then
|
||||
printf '%s\n' key >"$2"
|
||||
break
|
||||
fi
|
||||
shift
|
||||
done
|
||||
;;
|
||||
pkey)
|
||||
case " $* " in
|
||||
*' -pubout '*)
|
||||
printf '%s\n' '-----BEGIN PUBLIC KEY-----' 'UFVCS0VZ' \
|
||||
'-----END PUBLIC KEY-----'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
dgst)
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = '-out' ]; then
|
||||
printf '%s\n' signature >"$2"
|
||||
break
|
||||
fi
|
||||
shift
|
||||
done
|
||||
;;
|
||||
base64) printf '%s' U0lH ;;
|
||||
req)
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = '-out' ]; then
|
||||
printf '%s\n' '-----BEGIN CERTIFICATE REQUEST-----' 'Q1NS' \
|
||||
'-----END CERTIFICATE REQUEST-----' >"$2"
|
||||
break
|
||||
fi
|
||||
shift
|
||||
done
|
||||
;;
|
||||
s_client)
|
||||
cat >"$XCAT_TEST_CREDENTIAL_REQUEST"
|
||||
cat "$XCAT_TEST_CREDENTIAL_RESPONSE"
|
||||
;;
|
||||
x509) exit 0 ;;
|
||||
esac
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'timeout' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
shift
|
||||
exec "$@"
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'send-discovery' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
gzip -dc "$1" >"$XCAT_TEST_PACKET"
|
||||
printf '%s %s\n' "$2" "$3" >>"$XCAT_TEST_LOG"
|
||||
printf '%s\n' "$XCAT_TEST_RESPONSE" >"$XCAT_DISCOVERY_RESPONSE_FILE"
|
||||
SH
|
||||
write_file( File::Spec->catfile( $bin, 'network-refresh' ), <<'SH', 0755 );
|
||||
#!/bin/sh
|
||||
printf 'network-refresh %s\n' "$1" >>"$XCAT_TEST_LOG"
|
||||
SH
|
||||
|
||||
my %environment = (
|
||||
PATH => "$bin:$ENV{PATH}",
|
||||
XCAT_DISCOVERY_ATTEMPTS => 1,
|
||||
XCAT_DISCOVERY_RESPONSE_FILE => $response_file,
|
||||
XCAT_DISCOVERY_RETRY_SECONDS => 1,
|
||||
XCAT_DISCOVERY_SEND_COMMAND => File::Spec->catfile( $bin,
|
||||
'send-discovery' ),
|
||||
XCAT_KEY_DIR => $key_dir,
|
||||
XCAT_METADATA_FILE => $metadata_file,
|
||||
XCAT_NETWORK_FILE => $network_file,
|
||||
XCAT_NETWORK_REFRESH_COMMAND => File::Spec->catfile( $bin,
|
||||
'network-refresh' ),
|
||||
XCAT_PROC_ROOT => $proc_root,
|
||||
XCAT_STATE_DIR => $state_dir,
|
||||
XCAT_STATUS_COMMAND => $status_script,
|
||||
XCAT_STATUS_DIR => File::Spec->catdir( $state_dir, 'status' ),
|
||||
XCAT_SYS_ROOT => $root,
|
||||
XCAT_TEST_LOG => $command_log,
|
||||
XCAT_TEST_PACKET => $packet_file,
|
||||
XCAT_TEST_IPV4 => '192.0.2.98/24',
|
||||
XCAT_TEST_IPV6 => '',
|
||||
XCAT_TEST_CREDENTIAL_REQUEST => $credential_request,
|
||||
XCAT_TEST_CREDENTIAL_RESPONSE => $credential_response,
|
||||
XCAT_TEST_RESPONSE => 'restart (eth0)',
|
||||
XCAT_TEST_ARCH => 'x86_64',
|
||||
XCAT_UPTIME_FILE => $uptime,
|
||||
);
|
||||
|
||||
is( run_script( $discover_script, \%environment ), 0,
|
||||
'discovery accepts an xCAT node match' );
|
||||
my $packet = read_file($packet_file);
|
||||
like( $packet, qr{<command>findme</command>},
|
||||
'discovery sends a findme request' );
|
||||
like( $packet, qr{<arch>x86_64</arch>},
|
||||
'discovery sends the canonical architecture' );
|
||||
unlike( $packet, qr{<nodetype>virtual</nodetype>},
|
||||
'physical DMI does not claim a virtual node' );
|
||||
like( $packet, qr{<cpucount>2</cpucount>},
|
||||
'discovery reports the processor count' );
|
||||
like( $packet, qr{<cputype>Test CPU & Controller</cputype>},
|
||||
'discovery escapes inventory text' );
|
||||
like( $packet, qr{<memory>2048MB</memory>},
|
||||
'discovery reports memory' );
|
||||
like( $packet, qr{<disksize>vda:20GB</disksize>},
|
||||
'discovery reports disk capacity' );
|
||||
like( $packet,
|
||||
qr{<mtm>Acme & Co:Rack <Node></mtm>},
|
||||
'discovery escapes system identity' );
|
||||
like( $packet,
|
||||
qr{<mac>virtio_net\|eth0\|52:54:00:00:00:02\|192\.0\.2\.98/24</mac>},
|
||||
'discovery reports the management NIC' );
|
||||
like( $packet, qr{<location>Slot 4</location>},
|
||||
'discovery reports the NIC location' );
|
||||
like( $packet, qr{<switchname>switch01</switchname>},
|
||||
'discovery reports the LLDP neighbor' );
|
||||
like( $packet, qr{<switchport>Ethernet1/4</switchport>},
|
||||
'discovery reports the LLDP port' );
|
||||
like( $packet, qr{<bmcinband>1</bmcinband>},
|
||||
'discovery reports in-band BMC access' );
|
||||
like( $packet, qr{<bmc>192\.0\.2\.101</bmc>},
|
||||
'discovery reports a static BMC address' );
|
||||
like( $packet, qr{<bmcmac>52:54:00:aa:bb:cc</bmcmac>},
|
||||
'discovery reports the BMC MAC address' );
|
||||
like( $packet, qr{<sha512sig>\nU0lH\n</sha512sig>},
|
||||
'discovery signs the inventory' );
|
||||
is( read_file($command_log) =~ /192\.0\.2\.10 3001/ ? 1 : 0, 1,
|
||||
'discovery sends to the selected xCAT endpoint' );
|
||||
like( read_file($command_log), qr/^network-refresh restart \(eth0\)$/m,
|
||||
'discovery applies the assigned network identity' );
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'discovery.env' )
|
||||
),
|
||||
qr/^STATE=READY$/m,
|
||||
'discovery publishes completion'
|
||||
);
|
||||
|
||||
write_file( $network_file, <<'ENV' );
|
||||
XCATDEST=[2001:db8::10]:3001
|
||||
XCATMASTER=2001:db8::10
|
||||
XCATPORT=3001
|
||||
XCAT_INTERFACE=eth0
|
||||
XCAT_SOURCE_ADDRESS=2001:db8::98
|
||||
ENV
|
||||
$environment{XCAT_TEST_IPV4} = '';
|
||||
$environment{XCAT_TEST_IPV6} = '2001:db8::98/64';
|
||||
is( run_script( $discover_script, \%environment ), 0,
|
||||
'discovery accepts an IPv6-only network' );
|
||||
$packet = read_file($packet_file);
|
||||
like( $packet,
|
||||
qr{<mac>virtio_net\|eth0\|52:54:00:00:00:02\|2001:db8::98/64</mac>},
|
||||
'IPv6 supplies the legacy management NIC address' );
|
||||
like( $packet, qr{<ip6address>2001:db8::98/64</ip6address>},
|
||||
'discovery reports the IPv6 interface address' );
|
||||
unlike( $packet, qr{<ip4address>},
|
||||
'an IPv6-only interface does not claim an IPv4 address' );
|
||||
like( read_file($command_log), qr/^2001:db8::10 3001$/m,
|
||||
'discovery sends to the IPv6 xCAT endpoint' );
|
||||
$environment{XCAT_TEST_IPV4} = '192.0.2.98/24';
|
||||
$environment{XCAT_TEST_IPV6} = '';
|
||||
|
||||
write_file( File::Spec->catfile( $dmi_dir, 'sys_vendor' ), "QEMU\n" );
|
||||
$environment{XCAT_TEST_RESPONSE} = 'processed';
|
||||
isnt( run_script( $discover_script, \%environment ), 0,
|
||||
'an unmatched discovery request fails' );
|
||||
like( read_file($packet_file), qr{<nodetype>virtual</nodetype>},
|
||||
'virtual DMI is reported to xCAT' );
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'discovery.env' )
|
||||
),
|
||||
qr/^CODE=DISCOVERY_NOT_MATCHED$/m,
|
||||
'an unmatched node has a specific failure code'
|
||||
);
|
||||
|
||||
unlink(
|
||||
File::Spec->catfile( $dmi_dir, 'sys_vendor' ),
|
||||
File::Spec->catfile( $dmi_dir, 'product_name' ),
|
||||
File::Spec->catfile( $dmi_dir, 'product_serial' ),
|
||||
File::Spec->catfile( $dmi_dir, 'product_uuid' ),
|
||||
);
|
||||
my $device_tree = File::Spec->catdir( $proc_root, 'device-tree' );
|
||||
make_path($device_tree);
|
||||
write_file( File::Spec->catfile( $device_tree, 'model' ),
|
||||
"IBM,9009-42A\0" );
|
||||
write_file( File::Spec->catfile( $device_tree, 'system-id' ),
|
||||
"IBM,02AB123\0" );
|
||||
write_file( File::Spec->catfile( $proc_root, 'cpuinfo' ),
|
||||
"cpu : POWER9\ncpu : POWER9\nplatform : PowerNV\n" );
|
||||
$environment{XCAT_TEST_ARCH} = 'ppc64le';
|
||||
$environment{XCAT_TEST_RESPONSE} = 'restart';
|
||||
is( run_script( $discover_script, \%environment ), 0,
|
||||
'Power discovery accepts device-tree identity' );
|
||||
$packet = read_file($packet_file);
|
||||
like( $packet, qr{<arch>ppc64le</arch>},
|
||||
'Power discovery keeps the canonical architecture' );
|
||||
like( $packet, qr{<mtm>9009-42A</mtm>},
|
||||
'Power discovery reports the machine type' );
|
||||
like( $packet, qr{<serial>02AB123</serial>},
|
||||
'Power discovery reports the system serial' );
|
||||
like( $packet, qr{<platform>PowerNV</platform>},
|
||||
'Power discovery reports the firmware platform' );
|
||||
like( $packet, qr{<cpucount>2</cpucount>},
|
||||
'Power discovery counts processor records' );
|
||||
like( $packet,
|
||||
qr{<uuid>9009-42a-02ab123-525400000002</uuid>},
|
||||
'Power discovery creates a stable fallback UUID' );
|
||||
|
||||
unlink($response_file);
|
||||
is( run_script( $callback_script, \%environment, "restart (eth0)" ), 0,
|
||||
'callback accepts a restart response' );
|
||||
is( read_file($response_file), "restart (eth0)\n",
|
||||
'callback records the requested interface' );
|
||||
isnt( run_script( $callback_script, \%environment, "malformed" ), 0,
|
||||
'callback rejects an unknown response' );
|
||||
|
||||
write_file( $metadata_file, <<'ENV' );
|
||||
XCAT_NODE_NAME=node042
|
||||
XCAT_DESTINY=standby
|
||||
ENV
|
||||
write_file( $credential_response, <<'XML' );
|
||||
<xcatresponse>
|
||||
<data><content>
|
||||
-----BEGIN CERTIFICATE-----
|
||||
Q0VSVA==
|
||||
-----END CERTIFICATE-----
|
||||
</content></data>
|
||||
</xcatresponse>
|
||||
XML
|
||||
is( run_script( $getcert_script, \%environment ), 0,
|
||||
'certificate client installs an xCAT certificate' );
|
||||
is( read_file( File::Spec->catfile( $key_dir, 'cert.pem' ) ), <<'PEM',
|
||||
-----BEGIN CERTIFICATE-----
|
||||
Q0VSVA==
|
||||
-----END CERTIFICATE-----
|
||||
PEM
|
||||
'certificate client stores only the PEM certificate'
|
||||
);
|
||||
my $certificate_request = read_file($credential_request);
|
||||
like( $certificate_request, qr{<command>getcredentials</command>},
|
||||
'certificate client requests x509 credentials' );
|
||||
like( $certificate_request, qr{<callback_port>300</callback_port>},
|
||||
'certificate client declares the privileged callback port' );
|
||||
like( $certificate_request, qr{<sha512sig>\nU0lH\n</sha512sig>},
|
||||
'certificate request is signed by the discovery identity' );
|
||||
like( read_file($command_log),
|
||||
qr{openssl s_client -connect \[2001:db8::10\]:3001 -quiet},
|
||||
'certificate client keeps the bracketed IPv6 endpoint' );
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'credentials.env' )
|
||||
),
|
||||
qr/^STATE=READY$/m,
|
||||
'certificate client publishes completion'
|
||||
);
|
||||
|
||||
write_file( $metadata_file, "XCAT_NODE_NAME=invalid/name\n" );
|
||||
isnt( run_script( $getcert_script, \%environment ), 0,
|
||||
'certificate client rejects an unsafe node name' );
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'credentials.env' )
|
||||
),
|
||||
qr/^CODE=CERTIFICATE_NODE_IDENTITY_MISSING$/m,
|
||||
'missing confirmed identity has a specific failure code'
|
||||
);
|
||||
|
||||
is( run_script( $credential_callback_script, \%environment,
|
||||
"CREDOKBYYOU?" ),
|
||||
0, 'credential callback accepts the xCAT challenge' );
|
||||
is(
|
||||
read_callback_before_eof(
|
||||
$credential_callback_script, \%environment, "CREDOKBYYOU?\n"
|
||||
),
|
||||
"CREDOKBYME\n",
|
||||
'credential callback answers before xcatd closes the connection'
|
||||
);
|
||||
isnt( run_script( $credential_callback_script, \%environment, "unknown" ), 0,
|
||||
'credential callback rejects an unknown challenge' );
|
||||
|
||||
my $recipe = read_file(
|
||||
File::Spec->catfile( $discovery_dir, 'xcat-genesis-discovery_1.0.bb' )
|
||||
);
|
||||
like( $recipe, qr/^RDEPENDS:\$\{PN\} = "bash coreutils gzip iproute2 openssl-bin util-linux-lsblk"$/m,
|
||||
'discovery dependencies are explicit' );
|
||||
like( $recipe, qr/xcat-genesis-discovery\.socket/,
|
||||
'discovery callback socket is packaged' );
|
||||
like( $recipe, qr/xcat-genesis-credential\.socket/,
|
||||
'credential callback socket is packaged' );
|
||||
like( $recipe, qr/\$\{CC\}.*genesis-udp-send\.c/s,
|
||||
'discovery UDP sender is compiled for the target' );
|
||||
like( $recipe, qr/-std=c17.*-Werror/s,
|
||||
'discovery UDP sender uses the strict C build contract' );
|
||||
like( $recipe, qr{\$\{libexecdir\}/xcat/genesis-udp-send},
|
||||
'discovery UDP sender is packaged' );
|
||||
|
||||
my $udp_sender = read_file($udp_sender_source);
|
||||
like( $udp_sender, qr/^\s*SOURCE_PORT = 301,$/m,
|
||||
'discovery sender uses the legacy privileged source port' );
|
||||
like( $udp_sender, qr/^\s*hints\.ai_family = AF_UNSPEC;$/m,
|
||||
'discovery sender resolves both address families' );
|
||||
like( $udp_sender, qr/source4->sin_port = htons\(SOURCE_PORT\)/,
|
||||
'discovery sender binds the IPv4 source port' );
|
||||
like( $udp_sender, qr/source6->sin6_port = htons\(SOURCE_PORT\)/,
|
||||
'discovery sender binds the IPv6 source port' );
|
||||
like( read_file($discover_script), qr{/usr/libexec/xcat/genesis-udp-send},
|
||||
'discovery uses the source-port-aware UDP sender' );
|
||||
|
||||
my $image = read_file(
|
||||
File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core images xcat-genesis-image.bb)
|
||||
)
|
||||
);
|
||||
like( $image, qr/\bxcat-genesis-discovery\b/,
|
||||
'base image includes the discovery client' );
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use IO::Compress::Gzip qw(gzip $GzipError);
|
||||
use Test::More;
|
||||
|
||||
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
|
||||
my $report = File::Spec->catfile(
|
||||
$repo_root, qw(xCAT-genesis-builder oe report)
|
||||
);
|
||||
my $metrics = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-metrics)
|
||||
);
|
||||
my $recipe = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init xcat-genesis-init_1.0.bb)
|
||||
);
|
||||
my $service = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files xcat-genesis-metrics.service)
|
||||
);
|
||||
|
||||
sub write_file {
|
||||
my ( $path, $contents ) = @_;
|
||||
open( my $stream, '>', $path ) or die "Unable to write $path: $!";
|
||||
print {$stream} $contents;
|
||||
close($stream);
|
||||
}
|
||||
|
||||
sub read_file {
|
||||
my ($path) = @_;
|
||||
open( my $stream, '<', $path ) or die "Unable to read $path: $!";
|
||||
my $contents = do { local $/; <$stream> };
|
||||
close($stream);
|
||||
return $contents;
|
||||
}
|
||||
|
||||
sub run_command {
|
||||
my ( $environment, @command ) = @_;
|
||||
local %ENV = ( %ENV, %{$environment} );
|
||||
open( my $stream, '-|', @command )
|
||||
or die "Unable to run @command: $!";
|
||||
my $output = do { local $/; <$stream> };
|
||||
close($stream);
|
||||
return ( $? >> 8, $output );
|
||||
}
|
||||
|
||||
sub parse_report {
|
||||
my ($contents) = @_;
|
||||
my %values;
|
||||
foreach my $line ( split( /\n/, $contents ) ) {
|
||||
my ( $name, $value ) = split( /=/, $line, 2 );
|
||||
$values{$name} = $value;
|
||||
}
|
||||
return \%values;
|
||||
}
|
||||
|
||||
my $recipe_contents = read_file($recipe);
|
||||
like( $recipe_contents, qr/file:\/\/genesis-metrics/,
|
||||
'metrics collector is included in the init package' );
|
||||
like( $recipe_contents, qr/SYSTEMD_SERVICE.*?xcat-genesis-metrics\.service/s,
|
||||
'metrics service is enabled with the init package' );
|
||||
|
||||
my $service_contents = read_file($service);
|
||||
like( $service_contents,
|
||||
qr/Requires=xcat-genesis-register\.service\nAfter=xcat-genesis-register\.service/,
|
||||
'metrics are captured after successful registration' );
|
||||
like( $service_contents,
|
||||
qr{ExecStart=/usr/libexec/xcat/genesis-metrics --output /run/xcat/metrics\.env},
|
||||
'metrics are stored in the runtime state directory' );
|
||||
|
||||
my $root = tempdir( CLEANUP => 1 );
|
||||
my $registration = File::Spec->catfile( $root, 'registration.env' );
|
||||
my $meminfo = File::Spec->catfile( $root, 'meminfo' );
|
||||
my $uptime = File::Spec->catfile( $root, 'uptime' );
|
||||
my $runtime = File::Spec->catfile( $root, 'metrics.env' );
|
||||
|
||||
write_file(
|
||||
$registration,
|
||||
"SCHEMA=1\nSTATE=ACTION_RECEIVED\nDETAIL=Destiny shell\nUPDATED_SECONDS=42\n"
|
||||
);
|
||||
write_file(
|
||||
$meminfo,
|
||||
"MemTotal: 1048576 kB\nMemFree: 524288 kB\n"
|
||||
. "MemAvailable: 786432 kB\n"
|
||||
);
|
||||
write_file( $uptime, "45.92 12.34\n" );
|
||||
|
||||
my %metrics_environment = (
|
||||
XCAT_REGISTRATION_STATUS_FILE => $registration,
|
||||
XCAT_MEMINFO_FILE => $meminfo,
|
||||
XCAT_UPTIME_FILE => $uptime,
|
||||
);
|
||||
my ( $status, $output ) = run_command( \%metrics_environment, $metrics );
|
||||
is( $status, 0, 'runtime metrics are collected' );
|
||||
is_deeply(
|
||||
parse_report($output),
|
||||
{
|
||||
SCHEMA => '1',
|
||||
BOOT_READY_SECONDS => '42',
|
||||
CAPTURE_UPTIME_SECONDS => '45',
|
||||
MEMORY_TOTAL_KIB => '1048576',
|
||||
MEMORY_AVAILABLE_KIB => '786432',
|
||||
MEMORY_USED_KIB => '262144',
|
||||
},
|
||||
'runtime report records boot time and memory use'
|
||||
);
|
||||
|
||||
( $status, $output ) = run_command(
|
||||
\%metrics_environment, $metrics, '--output', $runtime
|
||||
);
|
||||
is( $status, 0, 'runtime metrics can be stored atomically' );
|
||||
is( read_file($runtime),
|
||||
"SCHEMA=1\nBOOT_READY_SECONDS=42\nCAPTURE_UPTIME_SECONDS=45\n"
|
||||
. "MEMORY_TOTAL_KIB=1048576\nMEMORY_AVAILABLE_KIB=786432\n"
|
||||
. "MEMORY_USED_KIB=262144\n",
|
||||
'stored runtime report is complete' );
|
||||
is( ( stat($runtime) )[2] & 07777, 0644,
|
||||
'stored runtime report is readable' );
|
||||
|
||||
my $image_dir = File::Spec->catdir( $root, 'image' );
|
||||
make_path($image_dir);
|
||||
my $kernel = File::Spec->catfile( $image_dir, 'kernel' );
|
||||
my $initramfs = File::Spec->catfile( $image_dir, 'initramfs.cpio.gz' );
|
||||
my $payload = "cpio payload\n";
|
||||
write_file( $kernel, "kernel\n" );
|
||||
gzip( \$payload => $initramfs )
|
||||
or die "Unable to create test initramfs: $GzipError";
|
||||
|
||||
( $status, $output ) = run_command(
|
||||
{}, $report, '--runtime', $runtime, 'x86_64', $image_dir
|
||||
);
|
||||
is( $status, 0, 'image and runtime metrics are reported together' );
|
||||
my $current = parse_report($output);
|
||||
is( $current->{ARCHITECTURE}, 'x86_64',
|
||||
'report records the Genesis architecture' );
|
||||
is( $current->{KERNEL_BYTES}, -s $kernel,
|
||||
'report measures the kernel' );
|
||||
is( $current->{COMPRESSED_INITRAMFS_BYTES}, -s $initramfs,
|
||||
'report measures the compressed initramfs' );
|
||||
is( $current->{UNPACKED_INITRAMFS_BYTES}, length($payload),
|
||||
'report measures the unpacked initramfs' );
|
||||
is( $current->{BOOT_READY_SECONDS}, 42,
|
||||
'report includes the registration-ready time' );
|
||||
is( $current->{MEMORY_USED_KIB}, 262144,
|
||||
'report includes captured memory use' );
|
||||
|
||||
my $baseline = File::Spec->catfile( $root, 'baseline.env' );
|
||||
write_file(
|
||||
$baseline,
|
||||
"SCHEMA=1\nARCHITECTURE=x86_64\nKERNEL_BYTES="
|
||||
. ( ( -s $kernel ) - 2 )
|
||||
. "\nCOMPRESSED_INITRAMFS_BYTES="
|
||||
. ( ( -s $initramfs ) - 3 )
|
||||
. "\nUNPACKED_INITRAMFS_BYTES="
|
||||
. ( length($payload) - 4 )
|
||||
. "\nBOOT_READY_SECONDS=40\nMEMORY_USED_KIB=250000\n"
|
||||
);
|
||||
|
||||
( $status, $output ) = run_command(
|
||||
{}, $report, '--runtime', $runtime, '--baseline', $baseline,
|
||||
'x86_64', $image_dir
|
||||
);
|
||||
is( $status, 0, 'report compares against a matching baseline' );
|
||||
my $comparison = parse_report($output);
|
||||
is( $comparison->{KERNEL_BYTES_DELTA}, 2,
|
||||
'kernel growth is reported' );
|
||||
is( $comparison->{COMPRESSED_INITRAMFS_BYTES_DELTA}, 3,
|
||||
'compressed image growth is reported' );
|
||||
is( $comparison->{UNPACKED_INITRAMFS_BYTES_DELTA}, 4,
|
||||
'unpacked image growth is reported' );
|
||||
is( $comparison->{BOOT_READY_SECONDS_DELTA}, 2,
|
||||
'slower registration is reported' );
|
||||
is( $comparison->{MEMORY_USED_KIB_DELTA}, 12144,
|
||||
'additional memory use is reported' );
|
||||
|
||||
done_testing();
|
||||
@@ -0,0 +1,552 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use IO::Socket::INET;
|
||||
use Test::More;
|
||||
|
||||
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
|
||||
my $network_script = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-network-state)
|
||||
);
|
||||
my $network_refresh_script = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-network-refresh)
|
||||
);
|
||||
my $register_script = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-register)
|
||||
);
|
||||
my $status_script = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-status)
|
||||
);
|
||||
my $maintenance_shell_script = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-maintenance-shell)
|
||||
);
|
||||
my $functions_file = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-functions)
|
||||
);
|
||||
my $getdestiny_script = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-scripts usr bin getdestiny)
|
||||
);
|
||||
my $nextdestiny_script = File::Spec->catfile(
|
||||
$repo_root,
|
||||
qw(xCAT-genesis-scripts usr bin nextdestiny)
|
||||
);
|
||||
|
||||
sub write_file {
|
||||
my ( $path, $contents, $mode ) = @_;
|
||||
open( my $fh, '>', $path ) or die "Unable to write $path: $!";
|
||||
print {$fh} $contents;
|
||||
close($fh);
|
||||
chmod( $mode, $path ) if defined($mode);
|
||||
}
|
||||
|
||||
sub read_file {
|
||||
my ($path) = @_;
|
||||
open( my $fh, '<', $path ) or die "Unable to read $path: $!";
|
||||
my $contents = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
return $contents;
|
||||
}
|
||||
|
||||
sub run_script {
|
||||
my ( $script, $environment, @arguments ) = @_;
|
||||
local %ENV = ( %ENV, %{$environment} );
|
||||
return system( '/bin/bash', $script, @arguments ) >> 8;
|
||||
}
|
||||
|
||||
sub capture_command {
|
||||
my ( $environment, @command ) = @_;
|
||||
local %ENV = ( %ENV, %{$environment} );
|
||||
open( my $fh, '-|', @command ) or die "Unable to run @command: $!";
|
||||
my $output = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
return ( $? >> 8, $output );
|
||||
}
|
||||
|
||||
my $root = tempdir( CLEANUP => 1 );
|
||||
my $bin = File::Spec->catdir( $root, 'bin' );
|
||||
my $state_dir = File::Spec->catdir( $root, 'run' );
|
||||
my $sys_class_net = File::Spec->catdir( $root, 'sys', 'class', 'net' );
|
||||
my $eth0 = File::Spec->catdir( $sys_class_net, 'eth0' );
|
||||
my $eth1 = File::Spec->catdir( $sys_class_net, 'eth1' );
|
||||
my $cmdline = File::Spec->catfile( $root, 'cmdline' );
|
||||
my $uptime = File::Spec->catfile( $root, 'uptime' );
|
||||
my $command_log = File::Spec->catfile( $root, 'commands.log' );
|
||||
my $destiny_response = File::Spec->catfile( $root, 'destiny-response.xml' );
|
||||
my $destiny_metadata = File::Spec->catfile( $state_dir, 'xcat-response.env' );
|
||||
my $listener = IO::Socket::INET->new(
|
||||
LocalAddr => '127.0.0.1',
|
||||
LocalPort => 0,
|
||||
Listen => 2,
|
||||
ReuseAddr => 1,
|
||||
) or die "Unable to open test listener: $!";
|
||||
my $listener_port = $listener->sockport();
|
||||
my $listener_pid = fork();
|
||||
die "Unable to fork test listener: $!" unless defined($listener_pid);
|
||||
if ( $listener_pid == 0 ) {
|
||||
for ( 1 .. 3 ) {
|
||||
my $client = $listener->accept() or exit 1;
|
||||
close($client);
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
|
||||
make_path( $bin, $state_dir, $eth0, $eth1 );
|
||||
write_file( File::Spec->catfile( $eth0, 'address' ),
|
||||
"52:54:00:00:00:35\n" );
|
||||
write_file( File::Spec->catfile( $eth0, 'operstate' ), "up\n" );
|
||||
write_file( $uptime, "123.45 456.78\n" );
|
||||
write_file(
|
||||
File::Spec->catfile( $bin, 'logger' ),
|
||||
"#!/bin/sh\nexit 0\n", 0755
|
||||
);
|
||||
write_file(
|
||||
File::Spec->catfile( $bin, 'ip' ),
|
||||
<<'SH', 0755
|
||||
#!/bin/sh
|
||||
case "$*" in
|
||||
'-4 -o route get '*) printf '%s\n' "$XCAT_TEST_ROUTE" ;;
|
||||
'-4 -o address show dev eth0 scope global')
|
||||
printf '%s\n' '2: eth0 inet 192.0.2.98/24 scope global eth0'
|
||||
;;
|
||||
esac
|
||||
SH
|
||||
);
|
||||
write_file(
|
||||
File::Spec->catfile( $bin, 'nmcli' ),
|
||||
<<'SH', 0755
|
||||
#!/bin/sh
|
||||
printf 'nmcli %s\n' "$*" >>"$XCAT_TEST_LOG"
|
||||
case "$*" in
|
||||
'-t -f DEVICE,TYPE device status')
|
||||
printf '%s\n' 'eth0:ethernet' 'eth1:ethernet' 'lo:loopback'
|
||||
;;
|
||||
'-g GENERAL.STATE device show '*) printf '%s\n' '100 (connected)' ;;
|
||||
'--terse --escape no -g IP4.DNS,IP6.DNS device show eth0')
|
||||
printf '%s\n' '192.0.2.53 | 2001:db8::53'
|
||||
;;
|
||||
esac
|
||||
SH
|
||||
);
|
||||
write_file(
|
||||
File::Spec->catfile( $bin, 'network-state-refresh' ),
|
||||
<<'SH', 0755
|
||||
#!/bin/sh
|
||||
printf '%s\n' 'network-state-refresh' >>"$XCAT_TEST_LOG"
|
||||
SH
|
||||
);
|
||||
write_file(
|
||||
File::Spec->catfile( $bin, 'getdestiny' ),
|
||||
<<'SH', 0755
|
||||
#!/bin/sh
|
||||
printf 'getdestiny %s\n' "$*" >>"$XCAT_TEST_LOG"
|
||||
metadata=
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--metadata) metadata=$2; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
if [ -n "$metadata" ]; then
|
||||
cat >"$metadata" <<'ENV'
|
||||
XCAT_NODE_NAME=node042
|
||||
XCAT_DESTINY=osimage=test-image
|
||||
XCAT_IMAGE_SERVER=192.0.2.10
|
||||
XCAT_KERNEL=/tftp/vmlinuz
|
||||
XCAT_INITRD=/tftp/initrd
|
||||
XCAT_KERNEL_COMMAND_LINE=console=ttyS0 quiet
|
||||
ENV
|
||||
fi
|
||||
printf '%s\n' "${XCAT_TEST_DESTINY-standby}"
|
||||
SH
|
||||
);
|
||||
write_file(
|
||||
File::Spec->catfile( $bin, 'openssl' ),
|
||||
<<'SH', 0755
|
||||
#!/bin/sh
|
||||
printf 'openssl %s\n' "$*" >>"$XCAT_TEST_LOG"
|
||||
cat "$XCAT_TEST_RESPONSE_FILE"
|
||||
SH
|
||||
);
|
||||
|
||||
my %environment = (
|
||||
PATH => "$bin:$ENV{PATH}",
|
||||
XCAT_CMDLINE_FILE => $cmdline,
|
||||
XCAT_STATE_DIR => $state_dir,
|
||||
XCAT_STATUS_COMMAND => $status_script,
|
||||
XCAT_STATUS_DIR => File::Spec->catdir( $state_dir, 'status' ),
|
||||
XCAT_SYS_CLASS_NET => $sys_class_net,
|
||||
XCAT_TEST_LOG => $command_log,
|
||||
XCAT_TEST_RESPONSE_FILE => $destiny_response,
|
||||
XCAT_TEST_ROUTE => '127.0.0.1 via 192.0.2.1 dev eth0 src 192.0.2.98',
|
||||
XCAT_UPTIME_FILE => $uptime,
|
||||
XCAT_GENESIS_FUNCTIONS => $functions_file,
|
||||
XCAT_REGISTRATION_ATTEMPTS => 1,
|
||||
XCAT_REGISTRATION_RETRY_SECONDS => 0,
|
||||
XCAT_REGISTRATION_REQUEST_TIMEOUT => 1,
|
||||
);
|
||||
|
||||
write_file(
|
||||
$destiny_response,
|
||||
<<'XML'
|
||||
<xcatresponse>
|
||||
<node>
|
||||
<name>node042</name>
|
||||
<destiny>osimage=test-image</destiny>
|
||||
<kernel>/tftp/vmlinuz</kernel>
|
||||
<initrd>/tftp/initrd</initrd>
|
||||
<kcmdline>console=ttyS0 quiet</kcmdline>
|
||||
<imgserver>192.0.2.10</imgserver>
|
||||
</node>
|
||||
</xcatresponse>
|
||||
XML
|
||||
);
|
||||
my ( $destiny_status, $destiny_output ) = capture_command(
|
||||
\%environment,
|
||||
'/bin/bash', $getdestiny_script,
|
||||
'192.0.2.10:3001', '--once', '--metadata', $destiny_metadata
|
||||
);
|
||||
is( $destiny_status, 0, 'getdestiny accepts a complete response' );
|
||||
is( $destiny_output, "osimage=test-image\n",
|
||||
'getdestiny preserves the complete action' );
|
||||
is(
|
||||
read_file($destiny_metadata),
|
||||
<<'ENV',
|
||||
XCAT_NODE_NAME=node042
|
||||
XCAT_DESTINY=osimage=test-image
|
||||
XCAT_IMAGE_SERVER=192.0.2.10
|
||||
XCAT_KERNEL=/tftp/vmlinuz
|
||||
XCAT_INITRD=/tftp/initrd
|
||||
XCAT_KERNEL_COMMAND_LINE=console=ttyS0 quiet
|
||||
ENV
|
||||
'getdestiny records the response metadata'
|
||||
);
|
||||
|
||||
write_file( $destiny_response, "<xcatresponse><error>denied</error></xcatresponse>\n" );
|
||||
( $destiny_status, $destiny_output ) = capture_command(
|
||||
\%environment,
|
||||
'/bin/bash', $getdestiny_script,
|
||||
'192.0.2.10:3001', '--once', '--metadata', $destiny_metadata
|
||||
);
|
||||
isnt( $destiny_status, 0, 'getdestiny rejects an error response' );
|
||||
is( read_file($destiny_metadata), <<'ENV',
|
||||
XCAT_NODE_NAME=node042
|
||||
XCAT_DESTINY=osimage=test-image
|
||||
XCAT_IMAGE_SERVER=192.0.2.10
|
||||
XCAT_KERNEL=/tftp/vmlinuz
|
||||
XCAT_INITRD=/tftp/initrd
|
||||
XCAT_KERNEL_COMMAND_LINE=console=ttyS0 quiet
|
||||
ENV
|
||||
'a failed request preserves prior metadata'
|
||||
);
|
||||
|
||||
write_file(
|
||||
$destiny_response,
|
||||
<<'XML'
|
||||
<xcatresponse>
|
||||
<node>
|
||||
<name>node042</name>
|
||||
<destiny>install rocky9.7-x86_64-compute</destiny>
|
||||
<kernel>/tftp/vmlinuz</kernel>
|
||||
<initrd>/tftp/initrd</initrd>
|
||||
<kcmdline>console=ttyS0 quiet</kcmdline>
|
||||
<imgserver>192.0.2.10</imgserver>
|
||||
</node>
|
||||
</xcatresponse>
|
||||
XML
|
||||
);
|
||||
( $destiny_status, $destiny_output ) = capture_command(
|
||||
\%environment,
|
||||
'/bin/bash', $nextdestiny_script,
|
||||
'192.0.2.10:3001', '--once', '--metadata', $destiny_metadata
|
||||
);
|
||||
is( $destiny_status, 0, 'nextdestiny accepts a complete response' );
|
||||
is( $destiny_output, "install rocky9.7-x86_64-compute\n",
|
||||
'nextdestiny preserves a legacy action with arguments' );
|
||||
like( read_file($destiny_metadata),
|
||||
qr/^XCAT_DESTINY=install rocky9\.7-x86_64-compute$/m,
|
||||
'nextdestiny records the complete action' );
|
||||
|
||||
write_file( $destiny_response,
|
||||
"<xcatresponse><error>chain unavailable</error></xcatresponse>\n" );
|
||||
( $destiny_status, $destiny_output ) = capture_command(
|
||||
\%environment,
|
||||
'/bin/bash', $nextdestiny_script,
|
||||
'192.0.2.10:3001', '--once'
|
||||
);
|
||||
is( $destiny_status, 0, 'nextdestiny returns a server error as an action' );
|
||||
is( $destiny_output, "error=chain unavailable\n",
|
||||
'nextdestiny preserves legacy error handling' );
|
||||
|
||||
write_file( $destiny_response, "<xcatresponse/>\n" );
|
||||
( $destiny_status, $destiny_output ) = capture_command(
|
||||
\%environment,
|
||||
'/bin/bash', $nextdestiny_script,
|
||||
'192.0.2.10:3001', '--once'
|
||||
);
|
||||
isnt( $destiny_status, 0, 'nextdestiny rejects an incomplete response' );
|
||||
|
||||
write_file(
|
||||
$cmdline,
|
||||
"xcatd=127.0.0.1:$listener_port BOOTIF=01-52-54-00-00-00-35\n"
|
||||
);
|
||||
is( run_script( $network_script, \%environment ), 0,
|
||||
'network state accepts DHCP boot state' );
|
||||
is(
|
||||
read_file( File::Spec->catfile( $state_dir, 'genesis.env' ) ),
|
||||
"XCATDEST=127.0.0.1:$listener_port\n"
|
||||
. "XCATMASTER=127.0.0.1\n"
|
||||
. "XCATPORT=$listener_port\n"
|
||||
. <<'ENV',
|
||||
XCAT_INTERFACE=eth0
|
||||
XCAT_SOURCE_ADDRESS=192.0.2.98
|
||||
XCAT_SOURCE_PREFIXED_ADDRESS=192.0.2.98/24
|
||||
XCAT_GATEWAY=192.0.2.1
|
||||
XCAT_DNS_SERVERS=192.0.2.53,2001:db8::53
|
||||
XCAT_NETWORK_METHOD=auto
|
||||
XCAT_LINK_STATE=up
|
||||
XCAT_MAC_ADDRESS=52:54:00:00:00:35
|
||||
XCAT_VERIFIED_SECONDS=123
|
||||
ENV
|
||||
'network state is written atomically'
|
||||
);
|
||||
is(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'network.env' )
|
||||
),
|
||||
<<'ENV',
|
||||
SCHEMA=1
|
||||
STATE=READY
|
||||
DETAIL=Management network ready on eth0
|
||||
STARTED_SECONDS=123
|
||||
UPDATED_SECONDS=123
|
||||
VERIFIED_SECONDS=123
|
||||
ENV
|
||||
'network readiness publishes status'
|
||||
);
|
||||
|
||||
write_file( $command_log, '' );
|
||||
$environment{XCAT_NETWORK_STATE_COMMAND} = File::Spec->catfile(
|
||||
$bin, 'network-state-refresh' );
|
||||
is( run_script( $network_refresh_script, \%environment, 'restart (eth0)' ),
|
||||
0, 'discovery refreshes its DHCP identity' );
|
||||
my $refresh_log = read_file($command_log);
|
||||
like( $refresh_log, qr/^nmcli --wait 10 device disconnect eth1$/m,
|
||||
'forced interface selection disconnects other Ethernet devices' );
|
||||
like( $refresh_log, qr/^nmcli --wait 10 device disconnect eth0$/m,
|
||||
'the discovery interface releases its temporary lease' );
|
||||
like( $refresh_log, qr/^nmcli --wait 30 device connect eth0$/m,
|
||||
'the discovery interface requests its assigned lease' );
|
||||
like( $refresh_log, qr/^network-state-refresh$/m,
|
||||
'network state is rebuilt after DHCP renewal' );
|
||||
delete $environment{XCAT_NETWORK_STATE_COMMAND};
|
||||
|
||||
write_file(
|
||||
$cmdline,
|
||||
"xcatd=127.0.0.1:$listener_port BOOTIF=01-52-54-00-00-00-35 "
|
||||
. 'hostip=192.0.2.98 netmask=255.255.255.192 '
|
||||
. "gateway=192.0.2.1\n"
|
||||
);
|
||||
write_file( $command_log, '' );
|
||||
is( run_script( $network_script, \%environment ), 0,
|
||||
'network state accepts static boot settings' );
|
||||
like( read_file($command_log), qr/ipv4\.addresses 192\.0\.2\.98\/26/,
|
||||
'dotted netmask becomes a CIDR prefix' );
|
||||
|
||||
write_file(
|
||||
$cmdline,
|
||||
"xcatd=127.0.0.1:$listener_port BOOTIF=01-52-54-00-00-00-35 "
|
||||
. 'hostip=2001:db8::98/64 netmask=64 '
|
||||
. "gateway=2001:db8::1\n"
|
||||
);
|
||||
write_file( $command_log, '' );
|
||||
is( run_script( $network_script, \%environment ), 0,
|
||||
'network state accepts static IPv6 boot settings' );
|
||||
like( read_file($command_log),
|
||||
qr/ipv4\.method disabled ipv6\.method manual ipv6\.addresses 2001:db8::98\/64 ipv6\.gateway 2001:db8::1/,
|
||||
'static IPv6 disables IPv4 and configures the IPv6 route' );
|
||||
|
||||
write_file(
|
||||
$cmdline,
|
||||
"xcatd=127.0.0.1:$listener_port BOOTIF=01-52-54-00-00-00-35 "
|
||||
. "hostip=192.0.2.98 netmask=255.255.255.0\n"
|
||||
);
|
||||
isnt( run_script( $network_script, \%environment ), 0,
|
||||
'partial static settings fail closed' );
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'network.env' )
|
||||
),
|
||||
qr/^STATE=FAILED$/m,
|
||||
'network failure replaces the prior status'
|
||||
);
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'network.env' )
|
||||
),
|
||||
qr/^CODE=INVALID_STATIC_NETWORK$/m,
|
||||
'network failure identifies the rejected configuration'
|
||||
);
|
||||
|
||||
$environment{XCATDEST} = '192.0.2.213:3001';
|
||||
write_file( $command_log, '' );
|
||||
write_file( $cmdline, "xcatd=192.0.2.213:3001 destiny=shell\n" );
|
||||
is( run_script( $register_script, \%environment ), 0,
|
||||
'registration accepts a kernel destiny' );
|
||||
is( read_file( File::Spec->catfile( $state_dir, 'destiny' ) ), "shell\n",
|
||||
'kernel destiny remains authoritative' );
|
||||
like( read_file($command_log), qr/getdestiny 192\.0\.2\.213:3001/,
|
||||
'kernel destiny still records xCAT contact' );
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'registration.env' )
|
||||
),
|
||||
qr/^STATE=ACTION_RECEIVED$/m,
|
||||
'registration records the received action'
|
||||
);
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'registration.env' )
|
||||
),
|
||||
qr/^ACTION=shell\nTARGET=\nNODE_NAME=node042$/m,
|
||||
'registration publishes confirmed identity and the selected action'
|
||||
);
|
||||
|
||||
write_file( $command_log, '' );
|
||||
write_file( $cmdline, "xcatd=192.0.2.213:3001\n" );
|
||||
is( run_script( $register_script, \%environment ), 0,
|
||||
'registration requests a missing destiny' );
|
||||
is( read_file( File::Spec->catfile( $state_dir, 'destiny' ) ), "standby\n",
|
||||
'xCAT supplies a missing destiny' );
|
||||
|
||||
$environment{XCAT_TEST_DESTINY} = '';
|
||||
isnt( run_script( $register_script, \%environment ), 0,
|
||||
'registration rejects an empty destiny' );
|
||||
is( read_file( File::Spec->catfile( $state_dir, 'destiny' ) ), "standby\n",
|
||||
'an empty destiny does not replace prior state' );
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'registration.env' )
|
||||
),
|
||||
qr/^STATE=FAILED$/m,
|
||||
'registration failure replaces the prior status'
|
||||
);
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'registration.env' )
|
||||
),
|
||||
qr/^CODE=XCAT_RESPONSE_UNAVAILABLE\nRECOVERY=/m,
|
||||
'registration failure includes a recovery hint'
|
||||
);
|
||||
|
||||
$environment{XCAT_TEST_DESTINY} = 'install rocky9.7-x86_64-compute';
|
||||
is( run_script( $register_script, \%environment ), 0,
|
||||
'registration accepts a legacy action with arguments' );
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'registration.env' )
|
||||
),
|
||||
qr/^ACTION=install\nTARGET=rocky9\.7-x86_64-compute$/m,
|
||||
'registration separates the action from its argument'
|
||||
);
|
||||
|
||||
{
|
||||
local %ENV = ( %ENV, %environment );
|
||||
is( system( '/bin/sh', $status_script, 'console', 'DEGRADED',
|
||||
"bad\n\tvalue\x01" ) >> 8,
|
||||
0, 'status helper accepts a valid record' );
|
||||
like(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'console.env' )
|
||||
),
|
||||
qr/^DETAIL=bad value$/m,
|
||||
'status detail is reduced to printable text'
|
||||
);
|
||||
write_file( $uptime, "130.00 500.00\n" );
|
||||
is( system( '/bin/sh', $status_script, 'console', 'DEGRADED',
|
||||
'waiting for operator', 'CODE=OPERATOR_WAIT',
|
||||
'RECOVERY=Review diagnostics' ) >> 8,
|
||||
0, 'status helper accepts structured fields' );
|
||||
is(
|
||||
read_file(
|
||||
File::Spec->catfile( $state_dir, 'status', 'console.env' )
|
||||
),
|
||||
<<'ENV',
|
||||
SCHEMA=1
|
||||
STATE=DEGRADED
|
||||
DETAIL=waiting for operator
|
||||
STARTED_SECONDS=123
|
||||
UPDATED_SECONDS=130
|
||||
CODE=OPERATOR_WAIT
|
||||
RECOVERY=Review diagnostics
|
||||
ENV
|
||||
'status helper preserves the stage start time'
|
||||
);
|
||||
open( my $saved_stderr, '>&', \*STDERR )
|
||||
or die "Unable to duplicate stderr: $!";
|
||||
open( STDERR, '>', File::Spec->devnull() )
|
||||
or die "Unable to redirect stderr: $!";
|
||||
isnt( system( '/bin/sh', $status_script, '../console', 'READY' ) >> 8,
|
||||
0, 'status helper rejects an unsafe component' );
|
||||
isnt( system( '/bin/sh', $status_script, 'console', 'UNKNOWN' ) >> 8,
|
||||
0, 'status helper rejects an unknown state' );
|
||||
isnt( system( '/bin/sh', $status_script, 'console', 'READY', '',
|
||||
'ATTEMPT=invalid' ) >> 8,
|
||||
0, 'status helper rejects invalid numeric fields' );
|
||||
open( STDERR, '>&', $saved_stderr )
|
||||
or die "Unable to restore stderr: $!";
|
||||
}
|
||||
|
||||
my $component_status_dir = File::Spec->catdir( $state_dir, 'status' );
|
||||
for my $component (qw(network extensions registration action)) {
|
||||
my $state = $component eq 'extensions' ? 'FAILED' :
|
||||
$component eq 'action' ? 'IDLE' : 'READY';
|
||||
write_file(
|
||||
File::Spec->catfile( $component_status_dir, "$component.env" ),
|
||||
"STATE=$state\n"
|
||||
);
|
||||
}
|
||||
my ( $shell_status, $shell_output ) = capture_command(
|
||||
\%environment,
|
||||
'/bin/bash', '-c',
|
||||
'printf "exit\\n" | /bin/bash "$1" 2>&1',
|
||||
'genesis-maintenance-shell', $maintenance_shell_script
|
||||
);
|
||||
is( $shell_status, 0, 'maintenance shell exits cleanly' );
|
||||
like(
|
||||
$shell_output,
|
||||
qr/^Overall state: FAILED$/m,
|
||||
'maintenance shell reports an extension failure as the overall state'
|
||||
);
|
||||
|
||||
write_file(
|
||||
File::Spec->catfile( $component_status_dir, 'extensions.env' ),
|
||||
"STATE=READY\n"
|
||||
);
|
||||
( $shell_status, $shell_output ) = capture_command(
|
||||
\%environment,
|
||||
'/bin/bash', '-c',
|
||||
'printf "exit\\n" | /bin/bash "$1" 2>&1',
|
||||
'genesis-maintenance-shell', $maintenance_shell_script
|
||||
);
|
||||
like(
|
||||
$shell_output,
|
||||
qr/^Overall state: IDLE$/m,
|
||||
'maintenance shell prefers the current action state'
|
||||
);
|
||||
|
||||
waitpid( $listener_pid, 0 );
|
||||
is( $? >> 8, 0, 'network readiness probes the xCAT port' );
|
||||
|
||||
done_testing();
|
||||
Reference in New Issue
Block a user