# 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 {    #'legacy' environment
                    $bphash->{$_}->[0]->{kernel} = "xcat/nbk.$arch";
                    $bphash->{$_}->[0]->{initrd} = "xcat/nkfs.$arch.gz";
                    $bphash->{$_}->[0]->{kcmdline} = $kcmdline . "xcatd=$master:$xcatdport";
            }
        }

    } 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;
