2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-08-26 16:46:41 +00:00

Merge pull request #7713 from VersatusHPC/fix/destiny-boot-sticky

fix(destiny): keep a node on boot when its destiny is advanced again
This commit is contained in:
Daniel Hilst
2026-08-19 06:49:26 -03:00
committed by GitHub
2 changed files with 85 additions and 1 deletions
+8 -1
View File
@@ -861,7 +861,14 @@ sub nextdestiny {
$ref->{currchain} = $ref->{chain};
} elsif ($ref->{currchain} !~ /[,;]/){
if ($ref->{currstate} and ($ref->{currchain} =~ /$ref->{currstate}/)) {
$ref->{currchain} = 'standby';
# 'boot' is the steady state a completed provision leaves behind,
# set by setdestiny once the install or netboot destiny is applied.
# Some installers advance the destiny more than once, so it has to
# survive being advanced again: replacing it with 'standby' would
# leave the node with no destiny that boots the installed system.
unless ($ref->{currchain} eq 'boot') {
$ref->{currchain} = 'standby';
}
$callnodeset = 0;
}
}
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use File::Spec;
use Test::More;
my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' );
my $plugin = File::Spec->catfile( $repo_root, 'xCAT-server/lib/xcat/plugins/destiny.pm' );
plan skip_all => "$plugin not found" unless -r $plugin;
open( my $fh, '<', $plugin ) or die "Unable to read $plugin: $!";
my $source = do { local $/; <$fh> };
close($fh);
# Extract the chain-advance block out of nextdestiny() and run it directly, so
# that this exercises the shipped logic instead of a copy that can drift away
# from it. The block only touches $ref and $callnodeset, so it can be evaluated
# without a database or a running xcatd.
my ($block) = $source =~ m{
( unless \s* \(\$ref->\{currchain\}\) .*?
\#If \s we've \s gone \s off \s the \s end \s of \s the \s chain .*?
\n \s* \} \n )
}sx;
ok( $block, 'the chain-advance block was located in nextdestiny()' )
or BAIL_OUT('destiny.pm no longer matches the expected chain-advance shape');
sub advance {
my (%chain) = @_;
my $ref = { %chain };
my $callnodeset = 1;
my $code = "sub { my (\$ref, \$callnodeset) = \@_;\n$block\n return (\$ref, \$callnodeset); }";
my $sub = eval $code;
die "Unable to evaluate the extracted block: $@" if $@;
my ( $out, $cns ) = $sub->( $ref, $callnodeset );
return $out;
}
# A completed provision leaves chain.currchain set to 'boot' (setdestiny does
# this once the install or netboot destiny is applied). Some installers advance
# the destiny more than once, and that extra advance must leave the node on a
# destiny that still boots it.
my $repeat = advance( currchain => 'boot', currstate => 'boot', chain => 'osimage=rhels9-x86_64-install-compute' );
is( $repeat->{currstate}, 'boot', 'advancing again from boot keeps the node booting' );
is( $repeat->{currchain}, 'boot', 'advancing again from boot leaves boot as the next destiny' );
# Advancing repeatedly has to stay idempotent, not drift one state per call.
my $twice = advance( currchain => $repeat->{currchain}, currstate => $repeat->{currstate}, chain => 'osimage=rhels9-x86_64-install-compute' );
is( $twice->{currstate}, 'boot', 'a third advance still leaves the node booting' );
is( $twice->{currchain}, 'boot', 'repeated advances from boot are idempotent' );
# An exhausted non-boot chain must still fall to standby, so that a finished
# install does not simply reinstall the node on its next boot.
my $exhausted = advance(
currchain => 'osimage=rhels9-x86_64-install-compute',
currstate => 'osimage=rhels9-x86_64-install-compute',
chain => 'osimage=rhels9-x86_64-install-compute'
);
is( $exhausted->{currstate}, 'standby', 'an exhausted install chain still falls to standby' );
# A chain with steps left is untouched by the guard and advances normally.
my $remaining = advance(
currchain => 'osimage=rhels9-x86_64-install-compute,boot',
currstate => 'osimage=rhels9-x86_64-install-compute',
chain => 'osimage=rhels9-x86_64-install-compute,boot'
);
is( $remaining->{currstate}, 'osimage=rhels9-x86_64-install-compute', 'a chain with steps left advances to its next step' );
is( $remaining->{currchain}, 'boot', 'a chain with steps left keeps the rest of the chain' );
# With no current chain the default chain is still copied in.
my $fresh = advance( currchain => '', currstate => '', chain => 'osimage=rhels9-x86_64-install-compute,boot' );
is( $fresh->{currstate}, 'osimage=rhels9-x86_64-install-compute', 'an empty currchain still starts from the default chain' );
done_testing();