#!/usr/bin/perl
# IBM(c) 2016 EPL license http://www.eclipse.org/legal/epl-v10.html

BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr'; }

use lib "$::XCATROOT/probe/lib/perl";
use probe_utils;
use File::Basename;
use Net::Ping;
use Getopt::Long qw(:config no_ignore_case);

use Data::Dumper;
use warnings;

my $program_name = basename("$0");
my $help;
my $noderange = undef;
my $test;
my $output  = "stdout";
my $verbose = 0;
my $rst     = 0;
my $interval = 5;
my $terminal = 0;
my $timeout = 0;
my $start_time;

#-----------------------------
# To store the node number of a specified type
# May like this:
# $type_nodesnum{pdu} = 2;
#                {switch} = 3;
#                {node} = 10;
#-----------------------------
my %type_nodesnum = ();

#-----------------------------
# To store the type, state hash
# May like this:
#  $state_node_hash{pdu}{state1}{pdu1} = 1;
#  $state_node_hash{pdu}{state1}{pdu2} = 1;
#  $state_node_hash{pdu}{state2}{pdu3} = 1;
#  $state_node_hash{pdu}{state3}{pdu4} = 1;
# The state include but not limited to below:
#    matching --> matched --> configured (For PDU/Switch)
#    matching --> matched --> installing --> booting --> booted
#    matching --> matched --> booting --> installing --> xxx --> booted
#   The terminal state is configured(For PDU/Switch) or booted(for Node)
#   matching means node found matching the range, but no status attribute value
#   matched means node found matching the range, with mac attribute value, but no status attribute value
#-----------------------------
my %state_node_hash = ();

# $state_node_hash{pdu}{state1}{number} = xx;
my %state_node_number = ();

#----------------------------
# To store the node based current state
# May like this:
#  $node_info{node1}{state} = "install";
#  $node_info{node1}{type} = "node";
#----------------------------
my %node_info = ();

#---------------------------
# To store nodes who haven't finished the status
#---------------------------
my %unfinished_nodes = ();
my %unmatched_nodes = ();

$::USAGE = "Usage:
    $program_name -h
    $program_name [-n noderange] [-V|--verbose] [-i|--interval <seconds>] [--timeout <seconds>]

Description:
    Use this command to get a summary of the cluster.

Options:
    -h : Get usage information of $program_name
    -n : Range of nodes to check. All node objects will be used if not specified.
    -i : The interval for screen refreshing, unit is second(s), 5 seconds by default.
    -V : To print additional debug information.
    --timeout: The timout if not all nodes finish, unit is second(s), no timeout by default.
";

#-------------------------------------
# main process
#-------------------------------------
if (
    !GetOptions("--help|h" => \$help,
        "T"                  => \$test,
        "V|verbose"          => \$VERBOSE,
        "d|discovery"        => \$DISCOVERY,
        "g|groupcount"       => \$GROUPCOUNT,
        "i|interval=s"         => \$interval,
        "timeout=s"            => \$timeout,
        "n=s"                => \$noderange))
{
    probe_utils->send_msg("$output", "f", "Invalid parameter for $program_name");
    probe_utils->send_msg("$output", "d", "$::USAGE");
    exit 1;
}

if ($help) {
    if ($output ne "stdout") {
        probe_utils->send_msg("$output", "d", "$::USAGE");
    } else {
        print "$::USAGE";
    }
    exit 0;
}

if ($test) {
    probe_utils->send_msg("$output", "o", "Use this command to get node summary in the cluster.");
    exit 0;
}

if (scalar(@ARGV) >= 1) {

    # After processing all the expected flags and arguments,
    # there is still left over stuff on the command line
    probe_utils->send_msg("$output", "f", "Invalid flag or parameter: @ARGV");
    probe_utils->send_msg("$output", "d", "$::USAGE");
    exit 1;
}
if (!$DISCOVERY and !$GROUPCOUNT) {
    if(!defined($noderange)) {
        $noderange = "all";
    }
} else {
    if (!defined($noderange)) {
        $noderange = "compute";
    }
    check_for_discovered_nodes() if ($DISCOVERY);
    groupcount_nodes() if ($GROUPCOUNT);
    exit 0;
}

$SIG{TERM} = $SIG{INT} = sub {
    $terminal = 1;
};
unless ($interval) {
    $interval = 5;
}

&check_nodes_attributes();
$start_time = time;
while (1) {
    update_nodes_info();
    alarm_output(1);
    if ($terminal) {
        alarm_output(0);
        last;
    }
    if ($timeout and (time() - $start_time > $timeout)) {
        alarm_output(0);
        last;
    }
    sleep $interval;
}
exit 0;
# Check for node definitions with MAC address defined
sub check_for_discovered_nodes {
    my $na = "N/A";
    my $rc = 0;

    my $all_nodes_mac = `lsdef -i mac -c $noderange 2> /dev/null`;
    chomp($all_nodes_mac);
    my @all_nodes_mac_lines = split("[\n\r]", $all_nodes_mac);

    if ($all_nodes_mac =~ /Usage:/) {

        # lsdef command displayed a Usage message. Must be some noderange formatting problem.
        # Issue a warning and exit.
        probe_utils->send_msg("$output", "w", "Can not get a list of nodes from specified noderange.");
        return 1;
    }

    if (scalar(@all_nodes_mac_lines) <= 0) {

        # There were no nodes matching the noderange. Issue a warning and exit.
        probe_utils->send_msg("$output", "w", "No nodes matching the noderange were found.");
        return 1;
    }

    # Go through the list of nodes and count how many have mac value
    my $mac_counter=0;
    foreach (@all_nodes_mac_lines) {
        # probe_utils->send_msg("$output", "d", "Processing $_.") if ($VERBOSE);
        my ($node_name, $value) = split ":", $_;
        my ($mac_name, $mac_value) = split "=", $value;
        if ($mac_value) {
            # mac if set for the node
            $mac_counter++;
            probe_utils->send_msg("$output", "d", "($mac_counter) $_") if ($VERBOSE);
        }
    }
    my $percent = sprintf("%.2f", (($mac_counter / scalar(@all_nodes_mac_lines)) * 100));

    probe_utils->send_msg("$output", "o", "$mac_counter out of " . scalar(@all_nodes_mac_lines) . " in the noderange \"$noderange\" have been discovered ($percent/%)");
    return $rc;
}

sub groupcount_nodes {
    my $na = "N/A";
    my $rc = 0;

    probe_utils->send_msg("$output", "w", "Group count function is not yet implemented.");
    return $rc;
}

sub alarm_output {
    my $flag = shift;
    if ($flag) {
        probe_utils->send_msg("$output", "xx", `clear`);
        my $time_elapsed = time - $start_time;
        probe_utils->send_msg("$output", "xx", "====".localtime()."($time_elapsed seconds Elapsed)");
    } else {
        probe_utils->send_msg("$output", "xx", "\nThe cluster state===============================");
    }
    foreach my $type (keys(%state_node_hash)) {
        unless ($type_nodesnum{$type}) {
            probe_utils->send_msg("$output", "w", "$type Total number: $type_nodesnum{$type}");
            next;
        }
        probe_utils->send_msg("$output", "xx", "$type(Total: $type_nodesnum{$type})--------------------------");
        foreach my $state (keys(%{$state_node_hash{$type}})) {
            my $node_number = scalar(keys %{$state_node_hash{$type}{$state}});
            if ($flag) {
                my $number = sprintf("%.2f", $node_number * 100.0/ $type_nodesnum{$type});
                probe_utils->send_msg("$output", "xx", "\t$state : $node_number($number%)");
            } else {
                probe_utils->send_msg("$output", "xx", "\t$state($node_number): ". join(",",keys %{$state_node_hash{$type}{$state}}) );
            }
        }
    }
}

sub update_nodes_info {
    if (keys(%unmatched_nodes)) {
        my $unmatched_noderange = join(",", keys(%unmatched_nodes));
        my @unmatched_nodes_attributes = `lsdef -i mac -c $unmatched_noderange 2> /dev/null`;
        foreach (@unmatched_nodes_attributes) {
            if (/^(.*):\s*mac=(.*)$/) {
                if ($2) {
                    update_node_info($1, "matched (no status)");
                }
            }
        }
    }
    if (keys(%unfinished_nodes)) {
        my $unfinished_noderange = join(",", keys(%unfinished_nodes));
        my @unfinished_nodes_attributes = `lsdef -i status -c $unfinished_noderange 2> /dev/null`;
        foreach (@unfinished_nodes_attributes) {
            if (/^(.*):\s*status=(.*)$/) {
                if ($2) {
                    update_node_info($1, "$2");
                }
            }
        }
    }
    unless(scalar keys(%unfinished_nodes)) {
        $terminal = 1;
    }
}

sub update_node_info {
    my $node = shift;
    my $state = shift;
    my $node_type = $node_info{$node}{type};
    my $node_state = $node_info{$node}{state};
    if ($state and $state ne '') {
        if (exists($unmatched_nodes{$node})) {
            delete($unmatched_nodes{$node});
            $unfinished_nodes{$node} = 1;
        }
    }
    if ($state eq $node_state) {
        return;
    }

    if (exists($state_node_hash{$node_type}{$node_state}{$node})) {
        delete($state_node_hash{$node_type}{$node_state}{$node});
    }
    unless (scalar keys (%{$state_node_hash{$node_type}{$node_state}})) {
        delete($state_node_hash{$node_type}{$node_state});
    }

    $state_node_hash{$node_type}{$state}{$node} = 1;

    $node_info{$node}{state} = $state;
    if ($state eq 'booted' or $state eq 'configured') {
        delete $unfinished_nodes{$node};
    }
}
sub check_nodes_attributes {
    my @nodes_attributes = `lsdef -i status,mac,mgt -c $noderange 2> /dev/null`;
    my %nodehash = ();
    foreach (@nodes_attributes) {
        if (/^(.*):\s*([^=]*)=(.*)$/) {
            $nodehash{$1}{$2}=$3;
        }
    }
    foreach (keys %nodehash) {
        if (!defined($nodehash{$_}{mgt})) {
            probe_utils->send_msg("$output", "w", "No 'mgt' set for node:$_");
            next;
        }
        if ($nodehash{$_}{status}) {
            $node_info{$_}{state} = $nodehash{$_}{status};
            $unfinished_nodes{$_} = 1;
        } elsif ($nodehash{$_}{mac}) {
            $node_info{$_}{state} = "matched (no status)";
            $unfinished_nodes{$_} = 1;
        } else {
            $node_info{$_}{state} = "matching (no status, no mac)";
            $unmatched_nodes{$_} = 1;
        }
        if ($nodehash{$_}{mgt} eq 'pdu') {
            $node_info{$_}{type} = 'pdu';
        } elsif($nodehash{$_}{mgt} eq 'switch') {
            $node_info{$_}{type} = 'switch';
        } else {
            $node_info{$_}{type} = 'node';
        }
        my $node_type = $node_info{$_}{type};
        if (!exists($type_nodesnum{$node_type})) {
            $type_nodesnum{$node_type} = 1;
        } else {
            $type_nodesnum{$node_type} += 1;
        }
        $state_node_hash{$node_type}{$node_info{$_}{state}}{$_} = 1;
    }
}
exit 0;
