2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-26 01:34:05 +00:00
Files
xcat-core/xCAT-server/lib/xcat/plugins/destiny.pm
T
Vinícius Ferrão 445257c500 fix(destiny): keep a node on boot when its destiny is advanced again
When a chain has run down to a single remaining step, nextdestiny replaces that
step with 'standby'. For a completed provision the remaining step is 'boot',
which setdestiny writes into chain.currchain once an install or netboot destiny
is applied, so the replacement discards the destiny that boots the node.

Some installers advance the destiny more than once. On the extra advance the
node silently moves from 'boot' to 'standby' and no longer has a destiny that
boots the system it just installed.

Keep 'boot' when it is the remaining step, and leave every other exhausted
chain falling to 'standby' as before, so a finished install still does not
reinstall the node on its next boot.

Co-authored-by: Jarrod Johnson <10814490+jjohnson42@users.noreply.github.com>
2026-08-06 23:30:25 -03:00

1035 lines
44 KiB
Perl

# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT_plugin::destiny;
BEGIN
{
$::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use xCAT::NodeRange;
use Data::Dumper;
use xCAT::Utils;
use xCAT::TableUtils;
use Sys::Syslog;
use xCAT::GlobalDef;
use xCAT::Table;
use xCAT_monitoring::monitorctrl;
use Getopt::Long;
use strict;
my $request;
my $callback;
my $subreq;
my $errored = 0;
#DESTINY SCOPED GLOBALS
my $chaintab;
my $iscsitab;
my $typetab;
my $restab;
#my $sitetab;
my $hmtab;
my $tftpdir = "/tftpboot";
my $nonodestatus = 0;
my %failurenodes = ();
#my $sitetab = xCAT::Table->new('site');
#if ($sitetab) {
#(my $ref1) = $sitetab->getAttribs({key => 'nodestatus'}, 'value');
my @entries = xCAT::TableUtils->get_site_attribute("nodestatus");
my $site_entry = $entries[0];
if (defined($site_entry)) {
if ($site_entry =~ /0|n|N/) { $nonodestatus = 1; }
}
#}
sub handled_commands {
return {
setdestiny => "destiny",
getdestiny => "destiny",
nextdestiny => "destiny"
}
}
sub process_request {
$request = shift;
$callback = shift;
$subreq = shift;
if (not $::XCATSITEVALS{disablecredfilecheck} and xCAT::Utils->isMN()) {
my $result = xCAT::TableUtils->checkCredFiles($callback);
}
if ($request->{command}->[0] eq 'getdestiny') {
xCAT::MsgUtils->trace(0, "d", "destiny->process_request: starting getdestiny...");
my @nodes;
if ($request->{node}) {
if (ref($request->{node})) {
@nodes = @{ $request->{node} };
} else {
@nodes = ($request->{node});
}
} else { # a client asking for it's own destiny.
unless ($request->{'_xcat_clienthost'}->[0]) {
$callback->({ destiny => ['discover'] });
return;
}
my ($node) = noderange($request->{'_xcat_clienthost'}->[0]);
unless ($node) { # it had a valid hostname, but isn't a node
$callback->({ destiny => ['discover'] });
return;
}
@nodes = ($node);
}
getdestiny(0, \@nodes);
} elsif ($request->{command}->[0] eq 'nextdestiny') {
xCAT::MsgUtils->trace(0, "d", "destiny->process_request: starting nextdestiny...");
nextdestiny(0, 1); #it is called within dodestiny
} elsif ($request->{command}->[0] eq 'setdestiny') {
xCAT::MsgUtils->trace(0, "d", "destiny->process_request: starting setdestiny...");
setdestiny($request, 0);
}
xCAT::MsgUtils->trace(0, "d", "destiny->process_request: processing is finished for " . $request->{command}->[0]);
}
sub relay_response {
my $resp = shift;
return unless ($resp);
$callback->($resp);
if ($resp and ($resp->{errorcode} and $resp->{errorcode}->[0]) or ($resp->{error} and $resp->{error}->[0])) {
$errored = 1;
}
my $failure = 0;
# Partial error on nodes, it allows to continue the rest of business on the sucessful nodes.
foreach (@{ $resp->{node} }) {
if ($_->{error} or $_->{errorcode}) {
$failure = 1;
if ($_->{name}) {
$failurenodes{$_->{name}->[0]} = 2;
}
}
}
if ( $failure ) {
$errored = $failure;
}
}
sub setdestiny {
my $req = shift;
my $flag = shift;
my $noupdate = shift;
$chaintab = xCAT::Table->new('chain', -create => 1);
my $bphash = $req->{bootparams};
@ARGV = @{ $req->{arg} };
my $noupdateinitrd;
my $ignorekernelchk;
#>>>>>>>used for trace log>>>>>>>
my $verbose;
GetOptions('noupdateinitrd' => \$noupdateinitrd,
'ignorekernelchk' => \$ignorekernelchk,
'V' => \$verbose); #>>>>>>>used for trace log>>>>>>>
#>>>>>>>used for trace log start>>>>>>>
my $verbose_on_off = 0;
if ($verbose) { $verbose_on_off = 1; }
#>>>>>>>used for trace log end>>>>>>>
if (@{ $req->{node} } == 0) {
xCAT::MsgUtils->trace($verbose, "d", "destiny->setdestiny: no nodes left to process, we are done");
return;
}
my @nodes = @{ $req->{node} };
my $bptab = xCAT::Table->new('bootparams', -create => 1);
my %tempbh = %{ $bptab->getNodesAttribs(\@nodes, [qw(addkcmdline)]) };
while(my ($key, $value) = each(%tempbh)) {
if ($value && $value->[0]->{"addkcmdline"}) {
my $addkcmdline = $value->[0]->{"addkcmdline"};
# $key is node name
$bphash->{$key}->[0]->{"addkcmdline"} = $addkcmdline;
}
}
$bptab->close();
my $state = $ARGV[0];
my $reststates;
my %nstates;
# to support the case that the state could be runimage=xxx,runimage=yyy,osimage=xxx
($state, $reststates) = split(/,/, $state, 2);
chomp($state);
if ($state eq "enact") {
my $nodetypetab = xCAT::Table->new('nodetype', -create => 1);
my %nodestates;
my %stents = %{ $chaintab->getNodesAttribs($req->{node}, "currstate") };
my %ntents = %{ $nodetypetab->getNodesAttribs($req->{node}, "provmethod") };
my $state;
my $sninit = 0;
if (exists($req->{inittime})) { # this is called in AAsn.pm
$sninit = $req->{inittime}->[0];
}
foreach (@{ $req->{node} }) { #First, build a hash of all of the states to attempt to keep things as aggregated as possible
if ($stents{$_}->[0]->{currstate}) {
$state = $stents{$_}->[0]->{currstate};
$state =~ s/ .*//;
#skip the node if state=ondiscover
if ($state eq 'ondiscover') {
next;
}
#get the osimagename if nodetype.provmethod has osimage specified
#use it for both sninit and genesis operating
if (($state eq 'install') || ($state eq 'netboot') || ($state eq 'statelite')) {
my $osimage = $ntents{$_}->[0]->{provmethod};
if (($osimage) && ($osimage ne 'install') && ($osimage ne 'netboot') && ($osimage ne 'statelite')) {
$state = "osimage=$osimage";
}
}
push @{ $nodestates{$state} }, $_;
}
}
foreach (keys %nodestates) {
$req->{arg}->[0] = $_;
$req->{node} = $nodestates{$_};
setdestiny($req, 30, 1); #ludicrous flag to denote no table updates can be inferred.
}
return;
} elsif ($state eq "next") {
return nextdestiny($flag + 1); #this is special case where updateflag is called
} elsif ($state eq "iscsiboot") {
my $iscsitab = xCAT::Table->new('iscsi');
unless ($iscsitab) {
$callback->({ error => "Unable to open iscsi table to get iscsiboot parameters", errorcode => [1], errorabort => [1] });
}
my $nodetype = xCAT::Table->new('nodetype');
my $ntents = $nodetype->getNodesAttribs($req->{node}, [qw(os arch profile)]);
my $ients = $iscsitab->getNodesAttribs($req->{node}, [qw(kernel kcmdline initrd)]);
foreach (@{ $req->{node} }) {
my $ient = $ients->{$_}->[0]; #$iscsitab->getNodeAttribs($_,[qw(kernel kcmdline initrd)]);
my $ntent = $ntents->{$_}->[0];
unless ($ient and $ient->{kernel}) {
unless ($ntent and $ntent->{arch} =~ /x86/ and -f ("$tftpdir/undionly.kpxe" or -f "$tftpdir/xcat/xnba.kpxe")) {
$failurenodes{$_} = 1;
xCAT::MsgUtils->report_node_error($callback, $_, "No iscsi boot data available");
} #If x86 node and undionly.kpxe exists, presume they know what they are doing
next;
}
$bphash->{kernel} = $ient->{kernel};
if ($ient->{initrd}) { $bphash->{initrd} = $ient->{initrd} }
if ($ient->{kcmdline}) { $bphash->{kcmdline} = $ient->{kcmdline} }
}
} elsif ($state =~ /ondiscover/) {
my $target;
if ($state =~ /=/) {
($state, $target) = split '=', $state, 2;
}
if(!$target){
$callback->({ error => "invalid argument: \"$state\"", errorcode => [1] });
return;
}
my @cmds = split '\|', $target;
foreach my $tmpnode (@{ $req->{node} }) {
foreach my $cmd (@cmds) {
my $action;
($cmd, $action) = split ':', $cmd;
my $runcmd = "$cmd $tmpnode $action";
xCAT::Utils->runcmd($runcmd, 0);
xCAT::MsgUtils->trace($verbose, "d", "run ondiscover command: $runcmd");
}
}
} elsif ($state =~ /^install[=\$]/ or $state eq 'install' or $state =~ /^netboot[=\$]/ or $state eq 'netboot' or $state eq "image" or $state eq "winshell" or $state =~ /^osimage/ or $state =~ /^statelite/) {
my $target;
my $action;
my $rawstate=$state;
if ($state =~ /=/) {
($state, $target) = split '=', $state, 2;
if(!$target){
$callback->({ error => "invalid argument: \"$rawstate\"", errorcode => [1] });
return;
}
if ($target =~ /:/) {
($target, $action) = split ':', $target, 2;
}
} else {
if ($state =~ /:/) {
($state, $action) = split ':', $state, 2;
}
}
xCAT::MsgUtils->trace($verbose, "d", "destiny->setdestiny: state=$state, target=$target, action=$action");
my %state_hash;
# 1, Set an initial state for all requested nodes
foreach my $tmpnode (@{ $req->{node} }) {
next if ($failurenodes{$tmpnode});
$state_hash{$tmpnode} = $state;
}
# 2, Filter those unsuitable nodes in 'state_hash'
my $nodetypetable = xCAT::Table->new('nodetype', -create => 1);
my $noderestable = xCAT::Table->new('noderes', -create => 1);
my $nbents = $noderestable->getNodeAttribs($req->{node}->[0], ["netboot"]); # It is assumed that all nodes has the same `netboot` attribute
my $curnetboot = $nbents->{netboot};
if ($state ne 'osimage') {
$callback->({ error => "The options \"install\", \"netboot\", and \"statelite\" have been deprecated, use \"osimage=<osimage_name>\" instead.", errorcode => [1], errorabort => [1] });
return;
my $updateattribs;
if ($target) {
my $archentries = $nodetypetable->getNodesAttribs($req->{node}, ['supportedarchs']);
if ($target =~ /^([^-]*)-([^-]*)-(.*)/) {
$updateattribs->{os} = $1;
$updateattribs->{arch} = $2;
$updateattribs->{profile} = $3;
my $nodearch = $2;
foreach (@{ $req->{node} }) {
if ($archentries->{$_}->[0]->{supportedarchs} and $archentries->{$_}->[0]->{supportedarchs} !~ /(^|,)$nodearch(\z|,)/) {
xCAT::MsgUtils->report_node_error($callback, $_,
"Requested architecture " . $nodearch . " is not one of the architectures supported by $_ (per nodetype.supportedarchs, it supports " . $archentries->{$_}->[0]->{supportedarchs} . ")"
);
$failurenodes{$_} = 1;
next;
}
} #end foreach
} else {
$updateattribs->{profile} = $target;
}
} #end if($target)
$updateattribs->{provmethod} = $state;
my @tmpnodelist = ();
foreach (@{ $req->{node} }) {
if ($failurenodes{$_}) {
delete $state_hash{$_};
next;
}
push @tmpnodelist, $_;
}
$nodetypetable->setNodesAttribs(\@tmpnodelist, $updateattribs);
} else { #state is osimage
if ($target) {
if (@{ $req->{node} } == 0) { return; }
my $osimagetable = xCAT::Table->new('osimage');
(my $ref) = $osimagetable->getAttribs({ imagename => $target }, 'provmethod', 'osvers', 'profile', 'osarch', 'imagetype');
if ($ref) {
if ($ref->{provmethod}) {
$state = $ref->{provmethod};
} else {
$callback->({ errorcode => [1], error => "osimage.provmethod for $target must be set.", errorabort => [1] });
return;
}
} else {
$callback->({ errorcode => [1], error => "Cannot find the OS image $target in the osimage table.", errorabort => [1] });
return;
}
my $netbootval = xCAT::Utils->lookupNetboot($ref->{osvers}, $ref->{osarch}, $ref->{imagetype});
unless ($netbootval =~ /$curnetboot/i) {
#$errored =1;
$callback->({ warning => [ join(",", @{ $req->{node} }) . ": $curnetboot might be invalid when provisioning $target,valid options: \"$netbootval\". \nFor more details see the 'netboot' description in the output of \"tabdump -d noderes\"." ] });
#return;
}
my $updateattribs;
$updateattribs->{provmethod} = $target;
$updateattribs->{profile} = $ref->{profile};
$updateattribs->{os} = $ref->{osvers};
$updateattribs->{arch} = $ref->{osarch};
my @tmpnodelist = ();
foreach ( @nodes ) {
if (exists($failurenodes{$_})) {
delete $state_hash{$_};
next;
}
$state_hash{$_} = $state;
push @tmpnodelist, $_;
}
$nodetypetable->setNodesAttribs(\@tmpnodelist, $updateattribs);
} else {
my $invalidosimghash;
my $updatestuff;
my $nodetypetable = xCAT::Table->new('nodetype', -create => 1);
my %ntents = %{ $nodetypetable->getNodesAttribs($req->{node}, "provmethod") };
foreach my $tmpnode (@nodes) {
next if (exists($failurenodes{$tmpnode}));
my $osimage = $ntents{$tmpnode}->[0]->{provmethod};
if (($osimage) && ($osimage ne 'install') && ($osimage ne 'netboot') && ($osimage ne 'statelite')) {
if (exists($updatestuff->{$osimage})) { #valid osimage
my $vnodes = $updatestuff->{$osimage}->{nodes};
push(@$vnodes, $tmpnode);
$state_hash{$tmpnode} = $updatestuff->{$osimage}->{state};
} elsif (exists($invalidosimghash->{$osimage})) { #valid osimage
push(@{ $invalidosimghash->{$osimage}->{nodes} }, $tmpnode);
next;
}
else { #Get a new osimage, to valid it and put invalid osimage into `invalidosimghash`
my $osimagetable = xCAT::Table->new('osimage');
(my $ref) = $osimagetable->getAttribs({ imagename => $osimage }, 'provmethod', 'osvers', 'profile', 'osarch', 'imagetype');
if ($ref) {
#check whether the noderes.netboot is set appropriately
#if not,push the nodes into $invalidosimghash->{$osimage}->{netboot}
my $netbootval = xCAT::Utils->lookupNetboot($ref->{osvers}, $ref->{osarch}, $ref->{imagetype});
if ($netbootval =~ /$curnetboot/i) {
1;
} else {
push(@{ $invalidosimghash->{$osimage}->{nodes} }, $tmpnode);
$invalidosimghash->{$osimage}->{netboot} = $netbootval;
next;
}
if ($ref->{provmethod} && $ref->{profile} && $ref->{osvers} && $ref->{osarch}) {
$state = $ref->{provmethod};
$state_hash{$tmpnode} = $state;
$updatestuff->{$osimage}->{state} = $state;
$updatestuff->{$osimage}->{nodes} = [$tmpnode];
$updatestuff->{$osimage}->{profile} = $ref->{profile};
$updatestuff->{$osimage}->{os} = $ref->{osvers};
$updatestuff->{$osimage}->{arch} = $ref->{osarch};
} else {
push(@{ $invalidosimghash->{$osimage}->{nodes} }, $tmpnode);
$invalidosimghash->{$osimage}->{error}->[0] = "osimage.provmethod, osimage.osvers, osimage.osarch and osimage.profile for $osimage must be set";
next;
}
} else {
push(@{ $invalidosimghash->{$osimage}->{nodes} }, $tmpnode);
$invalidosimghash->{$osimage}->{error}->[0] = "Cannot find the OS image $osimage in the osimage table";
next;
}
}
} else {
# not supported legacy mode
push(@{ $invalidosimghash->{__xcat_legacy_provmethod_mode}->{nodes} }, $tmpnode);
$invalidosimghash->{__xcat_legacy_provmethod_mode}->{error}->[0] = "OS image name must be specified in nodetype.provmethod";
next;
}
}
#if any node with inappropriate noderes.netboot,report the warning
foreach my $tmpimage (keys %$invalidosimghash) {
my @fnodes = @{ $invalidosimghash->{$tmpimage}->{nodes} };
for (@fnodes) {
$failurenodes{$_} = 1;
delete $state_hash{$_};
if ($invalidosimghash->{$tmpimage}->{error}) {
xCAT::MsgUtils->report_node_error($callback, $_, $invalidosimghash->{$tmpimage}->{error}->[0]);
}
}
my $netbootwarn = $invalidosimghash->{$tmpimage}->{netboot};
if ($netbootwarn) {
my $rsp;
$rsp->{warning}->[0] = join(",", @fnodes) . ": $curnetboot might be invalid when provisioning $tmpimage,valid options: \"$netbootwarn\". \nFor more details see the 'netboot' description in the output of \"tabdump -d noderes\".";
$callback->($rsp);
}
}
# upddate DB for the nodes which pass the checking
foreach my $tmpimage (keys %$updatestuff) {
my $updateattribs = $updatestuff->{$tmpimage};
my @tmpnodelist = @{ $updateattribs->{nodes} };
delete $updateattribs->{nodes}; #not needed for nodetype table
delete $updateattribs->{state}; #node needed for nodetype table
$nodetypetable->setNodesAttribs(\@tmpnodelist, $updateattribs);
}
}
if (%state_hash) { # To valide mac here
my @tempnodes = keys(%state_hash);
my $mactab = xCAT::Table->new('mac', -create => 1);
my $machash = $mactab->getNodesAttribs(\@tempnodes, ['mac']);
foreach (@tempnodes) {
my $macs = $machash->{$_}->[0];
unless ($macs and $macs->{mac}) {
$failurenodes{$_} = 1;
xCAT::MsgUtils->report_node_error($callback, $_, "No MAC address available for this node");
delete $state_hash{$_};
}
}
}
}
#print Dumper(\%state_hash);
my @validnodes = keys(%state_hash);
unless (@validnodes) {
# just return if no valid nodes left
$callback->({ errorcode => [1]});
return;
}
#if the postscripts directory exists then make sure it is
# world readable and executable by root; otherwise wget fails
my $installdir = xCAT::TableUtils->getInstallDir();
my $postscripts = "$installdir/postscripts";
if (-e $postscripts) {
my $cmd = "chmod -R a+r $postscripts";
xCAT::Utils->runcmd($cmd, 0);
my $rsp = {};
if ($::RUNCMD_RC != 0)
{
$callback->({ info => "$cmd failed" });
}
}
# 3, if precreatemypostscripts=1, create each mypostscript for each valid node
# otherwise, create it during installation /updatenode
my $notmpfiles = 0; # create tmp files if precreate=0
my $nofiles = 0; # create files, do not return array
my $reqcopy = {%$req};
$reqcopy->{node} = \@validnodes;
require xCAT::Postage;
xCAT::Postage::create_mypostscript_or_not($reqcopy, $callback, $subreq, $notmpfiles, $nofiles);
# 4, Issue the sub-request for each state in 'state_hash'
my %state_hash1;
foreach my $tmpnode (keys(%state_hash)) {
push @{ $state_hash1{ $state_hash{$tmpnode} } }, $tmpnode;
}
#print Dumper(\%state_hash1);
foreach my $tempstate (keys %state_hash1) {
my $samestatenodes = $state_hash1{$tempstate};
#print "state=$tempstate nodes=@$samestatenodes\n";
xCAT::MsgUtils->trace($verbose_on_off, "d", "destiny->setdestiny: issue mk$tempstate request");
$errored = 0;
$subreq->({ command => ["mk$tempstate"],
node => $samestatenodes,
noupdateinitrd => $noupdateinitrd,
ignorekernelchk => $ignorekernelchk,
bootparams => \$bphash}, \&relay_response);
if ($errored) {
# The error messeage for mkinstall/mknetboot/mkstatelite had been output within relay_response function above, don't need to output more
xCAT::MsgUtils->trace($verbose_on_off, "d", "destiny->setdestiny: Failed in processing mk$tempstate.");
next if ($errored > 1);
}
my $ntents = $nodetypetable->getNodesAttribs($samestatenodes, [qw(os arch profile)]);
my $updates;
foreach (@{$samestatenodes}) {
next if (exists($failurenodes{$_})); #Filter the failure nodes
$nstates{$_} = $tempstate; #local copy of state variable for mod
my $ntent = $ntents->{$_}->[0]; #$nodetype->getNodeAttribs($_,[qw(os arch profile)]);
if ($tempstate ne "winshell") {
if ($ntent and $ntent->{os}) {
$nstates{$_} .= " " . $ntent->{os};
} else {
xCAT::MsgUtils->report_node_error($callback, $_, "nodetype.os not defined for $_.");
$failurenodes{$_} = 1;
next;
}
} else {
$nstates{$_} .= " winpe";
}
if ($ntent and $ntent->{arch}) {
$nstates{$_} .= "-" . $ntent->{arch};
} else {
xCAT::MsgUtils->report_node_error($callback, $_, "nodetype.arch not defined for $_.");
$failurenodes{$_} = 1;
next;
}
if (($tempstate ne "winshell") && ($tempstate ne "sysclone")) {
if ($ntent and $ntent->{profile}) {
$nstates{$_} .= "-" . $ntent->{profile};
} else {
xCAT::MsgUtils->report_node_error($callback, $_, "nodetype.profile not defined for $_.");
$failurenodes{$_} = 1;
next;
}
}
$updates->{$_}->{'currchain'} = "boot";
}
unless ($tempstate =~ /^netboot|^statelite/) {
$chaintab->setNodesAttribs($updates);
}
if ($action eq "reboot4deploy") {
# this action is used in the discovery process for deployment of the node
# e.g. set chain.chain to 'osimage=rhels6.2-x86_64-netboot-compute:reboot4deploy'
# Set the status of the node to be 'installing' or 'netbooting'
my %newnodestatus;
my $newstat = xCAT_monitoring::monitorctrl->getNodeStatusFromNodesetState($tempstate, "rpower");
$newnodestatus{$newstat} = $samestatenodes;
xCAT_monitoring::monitorctrl::setNodeStatusAttributes(\%newnodestatus, 1);
}
}
} elsif ($state eq "shell" or $state eq "standby" or $state =~ /^runcmd/ or $state =~ /^runimage/) {
if ($state =~ /^runimage/) { # try to check the existence of the image for runimage
my @runimgcmds;
push @runimgcmds, $state;
if ($reststates) {
my @rstates = split(/,/, $reststates);
foreach (@rstates) {
if (/^runimage/) {
push @runimgcmds, $_;
}
}
}
foreach (@runimgcmds) {
my (undef, $path) = split(/=/, $_);
if ($path) {
if ($path =~ /\$/) { next; } # Ignore the path with including variable like $xcatmaster
my $cmd = "wget --spider --timeout 3 --tries=1 $path";
my @output = xCAT::Utils->runcmd("$cmd", -1);
unless (grep /^Remote file exists/, @output) {
$callback->({ error => ["Cannot wget $path. Verify it's downloadable."], errorcode => [1], errorabort => [1]});
return;
}
} else {
$callback->({ error => "An image path should be specified to runimage.", errorcode => [1], errorabort => [1] });
return;
}
}
}
$restab = xCAT::Table->new('noderes', -create => 1);
my $nodetype = xCAT::Table->new('nodetype');
#my $sitetab = xCAT::Table->new('site');
my $nodehm = xCAT::Table->new('nodehm');
my $hments = $nodehm->getNodesAttribs(\@nodes, [ 'serialport', 'serialspeed', 'serialflow' ]);
#(my $portent) = $sitetab->getAttribs({key=>'xcatdport'},'value');
my @entries = xCAT::TableUtils->get_site_attribute("xcatdport");
my $port_entry = $entries[0];
#(my $mastent) = $sitetab->getAttribs({key=>'master'},'value');
my @entries = xCAT::TableUtils->get_site_attribute("master");
my $master_entry = $entries[0];
my $enthash = $nodetype->getNodesAttribs(\@nodes, [qw(arch)]);
my $resents = $restab->getNodesAttribs(\@nodes, [qw(xcatmaster)]);
foreach (@nodes) {
my $ent = $enthash->{$_}->[0]; #$nodetype->getNodeAttribs($_,[qw(arch)]);
unless ($ent and $ent->{arch}) {
$failurenodes{$_} = 1;
xCAT::MsgUtils->report_node_error($callback, $_, "No archictecture defined in nodetype table for the node.");
next;
}
my $arch = $ent->{arch};
if ($arch eq "ppc64le" or $arch eq "ppc64el") {
$arch = "ppc64";
}
my $ent = $resents->{$_}->[0]; #$restab->getNodeAttribs($_,[qw(xcatmaster)]);
my $master;
my $kcmdline = "quiet ";
#the node.xcatmaster take precedence
if ($ent and $ent->{xcatmaster}) {
$master = $ent->{xcatmaster};
}
#if node.xcatmaster not specified, take the ip address facing the node
unless($master){
my @nxtsrvd = xCAT::NetworkUtils->my_ip_facing($_);
unless ($nxtsrvd[0]) {
$master = $nxtsrvd[1];
}
}
#the site.master takes the last precedence
unless($master){
if (defined($master_entry)) {
$master = $master_entry;
}
}
unless ($master) {
xCAT::MsgUtils->report_node_error($callback, $_, "No master in site table nor noderes table for the node.");
$failurenodes{$_} = 1;
next;
}
$ent = $hments->{$_}->[0]; #$nodehm->getNodeAttribs($_,['serialport','serialspeed','serialflow']);
if ($ent and defined($ent->{serialport})) {
if ($arch eq "ppc64") {
$kcmdline .= "console=tty0 console=hvc" . $ent->{serialport};
} else {
$kcmdline .= "console=tty0 console=ttyS" . $ent->{serialport};
}
#$ent = $nodehm->getNodeAttribs($_,['serialspeed']);
unless ($ent and defined($ent->{serialspeed})) {
xCAT::MsgUtils->report_node_error($callback, $_, "serialport defined, but no serialspeed for this node in nodehm table");
$failurenodes{$_} = 1;
next;
}
$kcmdline .= "," . $ent->{serialspeed};
#$ent = $nodehm->getNodeAttribs($_,['serialflow']);
$kcmdline .= " ";
}
my $xcatdport = "3001";
if (defined($port_entry)) {
$xcatdport = $port_entry;
}
if (-r "$tftpdir/xcat/genesis.kernel.$arch") {
my $bestsuffix = "lzma";
my $othersuffix = "gz";
if (-r "$tftpdir/xcat/genesis.fs.$arch.lzma" and -r "$tftpdir/xcat/genesis.fs.$arch.gz") {
if (-C "$tftpdir/xcat/genesis.fs.$arch.lzma" > -C "$tftpdir/xcat/genesis.fs.$arch.gz") { #here, lzma is older for whatever reason
$bestsuffix = "gz";
$othersuffix = "lzma";
}
}
if (-r "$tftpdir/xcat/genesis.fs.$arch.$bestsuffix") {
$bphash->{$_}->[0]->{kernel} = "xcat/genesis.kernel.$arch";
$bphash->{$_}->[0]->{initrd} = "xcat/genesis.fs.$arch.$bestsuffix";
$bphash->{$_}->[0]->{kcmdline} = $kcmdline . "xcatd=$master:$xcatdport destiny=$state";
} else {
$bphash->{$_}->[0]->{kernel} = "xcat/genesis.kernel.$arch";
$bphash->{$_}->[0]->{initrd} = "xcat/genesis.fs.$arch.$othersuffix";
$bphash->{$_}->[0]->{kcmdline} = $kcmdline . "xcatd=$master:$xcatdport destiny=$state";
}
} else { # genesis.kernel file is not there, assume 'legacy' environment
if (-r "$tftpdir/xcat/nbk.$arch") {
$bphash->{$_}->[0]->{kernel} = "xcat/nbk.$arch";
$bphash->{$_}->[0]->{initrd} = "xcat/nkfs.$arch.gz";
$bphash->{$_}->[0]->{kcmdline} = $kcmdline . "xcatd=$master:$xcatdport";
$callback->({ warning => ["No genesis.kernel.$arch file found. Defaulting to legacy nbk.$arch"]});
} else { # can not find genesis.kernel or nbk file
$callback->({ error => ["Could not find genesis.kernel.$arch or legacy nbk.$arch files"], errorcode => [1] });
exit(1);
}
}
}
} elsif ($state eq "offline" || $state eq "shutdown") {
1;
} elsif (!($state eq "boot")) {
$callback->({ error => ["Unknown state $state requested"], errorcode => [1] });
exit(1);
}
#Exclude the failure nodes
my @normalnodes = ();
foreach (@nodes) {
next if (exists($failurenodes{$_})); #Filter the failure nodes
push @normalnodes, $_;
}
unless (@normalnodes) {
return;
}
#blank out the nodetype.provmethod if the previous provisioning method is not 'install'
if ($state eq "iscsiboot" or $state eq "boot") {
my $nodetype = xCAT::Table->new('nodetype', -create => 1);
my $osimagetab = xCAT::Table->new('osimage', -create => 1);
my $ntents = $nodetype->getNodesAttribs(\@normalnodes, [qw(os arch profile provmethod)]);
my @nodestoblank = ();
my %osimage_hash = ();
foreach (@normalnodes) {
my $ntent = $ntents->{$_}->[0];
#if the previous nodeset staute is not install, then blank nodetype.provmethod
if ($ntent and $ntent->{provmethod}) {
my $provmethod = $ntent->{provmethod};
if (($provmethod ne 'install') && ($provmethod ne 'netboot') && ($provmethod ne 'statelite')) {
if (exists($osimage_hash{$provmethod})) {
$provmethod = $osimage_hash{$provmethod};
} else {
(my $ref) = $osimagetab->getAttribs({ imagename => $provmethod }, 'provmethod');
if (($ref) && $ref->{provmethod}) {
$osimage_hash{$provmethod} = $ref->{provmethod};
$provmethod = $ref->{provmethod};
}
}
}
#if ($provmethod ne 'install')
#fix bug: in sysclone, provmethod attribute gets cleared
if ($provmethod ne 'install' && $provmethod ne 'sysclone') {
push(@nodestoblank, $_);
}
}
} #end foreach
#now blank out the nodetype.provmethod
#print "nodestoblank=@nodestoblank\n";
if (@nodestoblank > 0) {
my $newhash;
$newhash->{provmethod} = "";
$nodetype->setNodesAttribs(\@nodestoblank, $newhash);
}
}
if ($noupdate) { return; } #skip table manipulation if just doing 'enact'
my $updates;
foreach (@normalnodes) {
my $lstate = $state;
if ($nstates{$_}) {
$lstate = $nstates{$_};
}
$updates->{$_}->{'currstate'} = $lstate;
# if there are multiple actions in the state argument, set the rest of states (shift out the first one)
# to chain.currchain so that the rest ones could be used by nextdestiny command
if ($reststates) {
$updates->{$_}->{'currchain'} = $reststates;
}
}
$chaintab->setNodesAttribs($updates);
return getdestiny($flag + 1, \@normalnodes);
}
sub nextdestiny {
my $flag = shift;
my $callnodeset = 0;
if (scalar(@_)) {
$callnodeset = 1;
}
my @nodes;
if ($request and $request->{node}) {
if (ref($request->{node})) {
@nodes = @{ $request->{node} };
} else {
@nodes = ($request->{node});
}
#TODO: service third party getdestiny..
} else { #client asking to move along its own chain
#TODO: SECURITY with this, any one on a node could advance the chain, for node, need to think of some strategy to deal with...
my $node;
if ($::XCATSITEVALS{nodeauthentication}) { #if requiring node authentication, this request will have a certificate associated with it, use it instead of name resolution
unless (ref $request->{username}) { return; } #TODO: log an attempt without credentials?
$node = $request->{username}->[0];
} else {
unless ($request->{'_xcat_clienthost'}->[0]) {
#ERROR? malformed request
xCAT::MsgUtils->trace(0, "d", "destiny->nextdestiny: Cannot determine the client from the received request");
return; #nothing to do here...
}
$node = $request->{'_xcat_clienthost'}->[0];
}
($node) = noderange($node);
unless ($node) {
#not a node, don't trust it
xCAT::MsgUtils->trace(0, "d", "destiny->nextdestiny: $node is not managed yet.");
return;
}
@nodes = ($node);
}
my $node;
my $noupdate_flag = 0;
$chaintab = xCAT::Table->new('chain');
my $chainents = $chaintab->getNodesAttribs(\@nodes, [qw(currstate currchain chain)]);
foreach $node (@nodes) {
unless ($chaintab) {
syslog("local4|err", "ERROR: $node requested destiny update, no chain table");
return; #nothing to do...
}
my $ref = $chainents->{$node}->[0]; #$chaintab->getNodeAttribs($node,[qw(currstate currchain chain)]);
unless ($ref->{chain} or $ref->{currchain}) {
syslog("local4|err", "ERROR: node requested destiny update, no path in chain.currchain");
return; #Can't possibly do anything intelligent..
}
unless ($ref->{currchain}) { #If no current chain, copy the default
$ref->{currchain} = $ref->{chain};
} elsif ($ref->{currchain} !~ /[,;]/){
if ($ref->{currstate} and ($ref->{currchain} =~ /$ref->{currstate}/)) {
# '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;
}
}
my @chain = split /[,;]/, $ref->{currchain};
$ref->{currstate} = shift @chain;
$ref->{currchain} = join(',', @chain);
unless ($ref->{currchain}) { #If we've gone off the end of the chain, have currchain stick
$ref->{currchain} = $ref->{currstate};
}
$chaintab->setNodeAttribs($node, $ref); #$ref is in a state to commit back to db
my %requ;
$requ{node} = [$node];
$requ{arg} = [ $ref->{currstate} ];
if ($ref->{currstate} =~ /noupdateinitrd$/)
{
my @items = split /[:]/, $ref->{currstate};
$requ{arg} = \@items;
$noupdate_flag = 1;
}
setdestiny(\%requ, $flag + 1);
}
if ($callnodeset) {
my $args;
if ($noupdate_flag)
{
$args = [ 'enact', '--noupdateinitrd' ];
}
else
{
$args = ['enact'];
}
$subreq->({ command => ['nodeset'],
node => \@nodes,
arg => $args });
}
}
sub getdestiny {
my $flag = shift;
my $nodes = shift;
# flag value:
# 0--getdestiny is called by dodestiny
# 1--called by nextdestiny in dodestiny. The node calls nextdestiny before boot and runcmd.
# 2--called by nodeset command
# 3--called by updateflag after the node finished installation and before booting
my $node;
xCAT::MsgUtils->trace(0, "d", "destiny->process_request: getdestiny...");
$restab = xCAT::Table->new('noderes');
my $chaintab = xCAT::Table->new('chain');
my $chainents = $chaintab->getNodesAttribs($nodes, [qw(currstate chain)]);
my $nrents = $restab->getNodesAttribs($nodes, [qw(tftpserver xcatmaster)]);
my $bptab = xCAT::Table->new('bootparams', -create => 1);
my $bpents = $bptab->getNodesAttribs($nodes, [qw(kernel initrd kcmdline xcatmaster)]);
#my $sitetab= xCAT::Table->new('site');
#(my $sent) = $sitetab->getAttribs({key=>'master'},'value');
my @entries = xCAT::TableUtils->get_site_attribute("master");
my $master_value = $entries[0];
my %node_status = ();
foreach $node (@$nodes) {
unless ($chaintab) { #Without destiny, have the node wait with ssh hopefully open at least
my $stat = xCAT_monitoring::monitorctrl->getNodeStatusFromNodesetState("standby", "getdestiny");
if ($stat) {
if (exists($node_status{$stat})) {
push @{ $node_status{$stat} }, $node;
} else {
$node_status{$stat} = [$node];
}
xCAT_monitoring::monitorctrl::setNodeStatusAttributes(\%node_status, 1);
}
$callback->({ node => [ { name => [$node], data => ['standby'], destiny => ['standby'] } ] });
return;
}
my $ref = $chainents->{$node}->[0]; #$chaintab->getNodeAttribs($node,[qw(currstate chain)]);
unless ($ref) {
#collect node status for certain states
if (($nonodestatus == 0) && (($flag == 0) || ($flag == 3))) {
my $stat = xCAT_monitoring::monitorctrl->getNodeStatusFromNodesetState("standby", "getdestiny");
#print "node=$node, stat=$stat\n";
if ($stat) {
if (exists($node_status{$stat})) {
push @{ $node_status{$stat} }, $node;
} else {
$node_status{$stat} = [$node];
}
}
}
$callback->({ node => [ { name => [$node], data => ['standby'], destiny => ['standby'] } ] });
next;
}
unless ($ref->{currstate}) { #Has a record, but not yet in a state...
# we set a 1 here so that it does the nodeset to create tftpboot files
return nextdestiny(0, 1); #Becomes a nextdestiny...
# my @chain = split /,/,$ref->{chain};
# $ref->{currstate} = shift @chain;
# $chaintab->setNodeAttribs($node,{currstate=>$ref->{currstate}});
}
my %response;
$response{name} = [$node];
$response{data} = [ $ref->{currstate} ];
$response{destiny} = [ $ref->{currstate} ];
my $nrent = $nrents->{$node}->[0]; #$noderestab->getNodeAttribs($node,[qw(tftpserver xcatmaster)]);
my $bpent = $bpents->{$node}->[0]; #$bptab->getNodeAttribs($node,[qw(kernel initrd kcmdline xcatmaster)]);
if (defined $bpent->{kernel}) {
$response{kernel} = $bpent->{kernel};
}
if (defined $bpent->{initrd}) {
$response{initrd} = $bpent->{initrd};
}
if (defined $bpent->{kcmdline}) {
$response{kcmdline} = $bpent->{kcmdline};
}
if (defined $nrent->{tftpserver}) {
$response{imgserver} = $nrent->{tftpserver};
} elsif (defined $nrent->{xcatmaster}) {
$response{imgserver} = $nrent->{xcatmaster};
} elsif (defined($master_value)) {
$response{imgserver} = $master_value;
} else {
my @resd = xCAT::NetworkUtils->my_ip_facing($node);
unless ($resd[0]) { $response{imgserver} = $resd[1]; }
}
#collect node status for certain states
if (($flag == 0) || ($flag == 3)) {
my $stat = xCAT_monitoring::monitorctrl->getNodeStatusFromNodesetState($response{destiny}->[0], "getdestiny");
#print "node=$node, stat=$stat\n";
if ($stat) {
if (exists($node_status{$stat})) {
push @{ $node_status{$stat} }, $node;
} else {
$node_status{$stat} = [$node];
}
}
}
$callback->({ node => [ \%response ] });
}
#setup the nodelist.status
if (($nonodestatus == 0) && (($flag == 0) || ($flag == 3))) {
#print "save status\n";
if (keys(%node_status) > 0) { xCAT_monitoring::monitorctrl::setNodeStatusAttributes(\%node_status, 1); }
}
}
1;