2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2025-05-21 19:22:05 +00:00
2024-05-07 16:19:25 +02:00

1028 lines
43 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}/)) {
$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;