#!/usr/bin/perl -w
###########################################################################
# (C) Copyright 2013-2015 Hewlett-Packard Development Company, L.P.
# @(#) Utility for displaying KPI of Serviceguard objects(cluster/node/
#      package) by Serviceguard Cluster Analytics's Daemon.
# @(#) Product Name     : HP Serviceguard Cluster Analytics 
# @(#) Product Version  : A.12.10.00
# @(#) Patch Name       : 
#
#
# *** Note: This file MUST NOT be edited. *****
#
# Any changes made to it will be overwritten when you upgrade to the
# next release of HP Serviceguard Cluster Analytics.
#
# Changing this file may lead to unrecoverable
# Cluster Analytics daemon failure or may lead to issues related
# to administration of Cluster Analytics daemon and displaying 
# KPIs 
#
###########################################################################

use strict;

use Pod::Usage;
use File::Path;
use Errno qw(EAGAIN);
use Sys::Hostname;
use Getopt::Long qw(GetOptions);

=head1 NAME

cmcashowkpi - command to retrieve KPIs for objects. 

=cut

# agruments
# -o {cluster|node|package} 
# -s <start_time>
# -e <end_time>
# example:-    
# cmcashowkpi -o node node1 node2
# cmcashowkpi -o package -s 2001-10-20\ 10:10:20 pkg1 pkg2 

=head1 SYNOPSIS

cmcashowkpi [-s start_time] [-e end_time] [-o {cluster|package|node} [object_values]

=cut

my $oper = undef;
my $nodeName = undef;
my $errors = 0;
my @file_out = undef;
my $SGSBIN = undef;
my $SGCONF = undef;
my $hostname = hostname();
$hostname =~ s/\..*//; 
my $cmd = "";
my @nodelist = ();
my %cmexecOutput;
my %cmexecFiles;
my $cmd_retval=0;
my $cl_exists_on_local_node;
my @cmviewcl_lineoutput = ();
my $sargs=undef;
my $eargs=undef;
my $debug_args=undef;
my $object_type_args=undef;
my $cashowkpi_cmd = "";
my $cashowkpi_cmd_args = "";
my $arg_errors = 0;
my $object_list_args="";
my $finput_args=undef;
my $cmcashowkpi_cmd = "";
my $opt_dmn_start_t = 0;
#########################################
# ERROR number mapped to errno defined in
# cmanalytics.h header file.
#########################################
use constant CM_CA_ERR_UNSUPPORTED_OS    => 39;
use constant CM_CA_ERR_CMCLNODELIST      => 40;
use constant CM_CA_ERR_NOTFOUND_CMCLNODELIST => 41;
use constant CM_CA_ERR_READ_CDB          => 42;
use constant CM_CA_ERR_INCONSISTENCY_CDB => 43;

#########################################
GetOptions(
    's=s' => sub { non_dash_arg(@_, \$sargs) },
    'e=s' => sub { non_dash_arg(@_, \$eargs) },
    'D=s' => sub { non_dash_arg(@_, \$debug_args) },
    'o=s'   => sub { non_dash_arg(@_, \$object_type_args)},
    'f=s' => sub { non_dash_arg(@_,\$finput_args)},
    'g'   => \$opt_dmn_start_t,
    "h|help|?" => sub { pod2usage() }
) ||  pod2usage();

if ($arg_errors) {
    print STDERR "ERROR: Error in specifying command line arguments\n";
    pod2usage();
}
if(!defined($object_type_args) && !$opt_dmn_start_t) {
    print STDERR "ERROR: Object type needs to be provided to the command\n";
    pod2usage();
}

my $sgconffile = "/etc/cmcluster.conf";
if (-f $sgconffile) {
    @file_out = qx(cat $sgconffile);
} else {
    print STDERR "ERROR: Unable to run cmcashowkpi ".
                 "command on unsupported OS\n";
    exit(CM_CA_ERR_UNSUPPORTED_OS);	                 
}

my @sgsbin_out = grep(/^SGSBIN/, @file_out);
if (@sgsbin_out) {
    my @split_sgsbin_name = split(/=/,$sgsbin_out[0]);
    chomp($split_sgsbin_name[1]);
    $SGSBIN = $split_sgsbin_name[1];
} else {
    print STDERR "ERROR: Unable to run cmcashowkpi command. ".
                 "Failed to source SGSBIN\n";
    exit(1);	                 
}    

my @sgconf_out = grep(/^SGCONF/, @file_out);
if (@sgconf_out) {
    my @split_sgconf_name = split(/=/,$sgconf_out[0]);
    chomp($split_sgconf_name[1]);
    $SGCONF = $split_sgconf_name[1];
} else {
    print STDERR "ERROR: Unable to run cmcashowkpi command. ".
                 "Failed to source SGCONF\n";
    exit(1);	                 
}    

if ($opt_dmn_start_t) {
    $cashowkpi_cmd_args = $cashowkpi_cmd_args . " -g";
} else {
    $cashowkpi_cmd_args = $cashowkpi_cmd_args . " -o " . $object_type_args;
    # Whitespaces provided with -s option need to be escaped with "\".
    if(defined($sargs)) {
       $sargs =~ s/ /\\ /g;
       $cashowkpi_cmd_args = $cashowkpi_cmd_args . " -s " . $sargs;
    }
    # Whitespaces provided with -e option need to be escaped with "\".
    if(defined($eargs)) {
       $eargs =~ s/ /\\ /g;
       $cashowkpi_cmd_args = $cashowkpi_cmd_args . " -e " . $eargs;
    }
    if(defined($debug_args)) {
       $cashowkpi_cmd_args = $cashowkpi_cmd_args . " -D " . $debug_args;
    }

    # Remaining arguement are object list arguements.
    foreach my $arg1 (@ARGV) {
        $object_list_args = $object_list_args . " ". $arg1;
    }
    if (defined($finput_args)) {
        $cashowkpi_cmd_args .= " " . "-f line ";
    }
    $cashowkpi_cmd_args =  $cashowkpi_cmd_args . " " . $object_list_args;
}
$cmcashowkpi_cmd = "$SGSBIN/cashowkpi " . $cashowkpi_cmd_args;

$cl_exists_on_local_node = check_if_cluster_exists();
if ($cl_exists_on_local_node == 0) {
    my $showkpi_node_name="";
    if(open (NODELISTFILE, "$SGCONF/"."cmclnodelist")) {
        my @nodelist_entries = <NODELISTFILE>;    
        @nodelist = ();
        foreach my $entry (@nodelist_entries) {
            chomp($entry);
            if($entry && !($entry =~ m/^#/)) {
                $entry =~ s/^\s+//;
                my @nodevals = split(' ', $entry);
                if (!($nodevals[0] =~ m/\+/)) {
                    push(@nodelist, $nodevals[0]);
                }
            }
        }
        if(scalar(@nodelist) == 0) {
            print STDERR "ERROR: Unable to process entries in ";
            print STDERR "$SGCONF/cmclnodelist\n";
            exit(CM_CA_ERR_CMCLNODELIST);
        }
        my %seen = ();
        my @uniq_node_name_list = grep { !$seen{$_}++ } @nodelist;    
        my @sorted_node_list = sort(@uniq_node_name_list);
        $showkpi_node_name = $sorted_node_list[0];
        close(NODELISTFILE);
    }
    else {
        print STDERR "Node $hostname must have $SGCONF/cmclnodelist ";
        print STDERR "populated to run the command\n";
        exit(CM_CA_ERR_NOTFOUND_CMCLNODELIST);
    }
    $cmd_retval = run_showkpi_cmd($showkpi_node_name); 
} 
else {
    $cmd = "$SGSBIN/cmviewcl" . " -v -f line";
    my $retval = 0 ;
    ($retval,@cmviewcl_lineoutput) = runandCaptureCommand($cmd);
    if ($retval) {
        print STDERR "ERROR: Unable to retrieve cluster configuration ";
        print STDERR "information\n";
        exit(CM_CA_ERR_READ_CDB);
    }
    # check if the coordinator is available. if yes then the start the 
    # analytics daemon on that node.
    my @coordinator_lines = grep {/coordinator=/} @cmviewcl_lineoutput; 
    if (scalar(@coordinator_lines) ne 0) {
        chomp($coordinator_lines[0]);
        my @nodevals = split('=',$coordinator_lines[0]);
        $cmd_retval = run_showkpi_cmd($nodevals[1]); 
    }
    else {
        my @node_id_lines = grep {/node:[a-zA-Z0-9_\\-]+\|id=1$/} @cmviewcl_lineoutput;
        if (scalar(@node_id_lines)>1) {
            print STDERR "ERROR: Cluster configuration has inconsistency\n";
            exit(CM_CA_ERR_INCONSISTENCY_CDB);    
        }
        chomp($node_id_lines[0]);
        my @nodenames = split('\|',$node_id_lines[0]);
        chomp($nodenames[0]);
        my @nodevals = split(':',$nodenames[0]);
        $cmd_retval = run_showkpi_cmd($nodevals[1]);
    }
}
if($cmd_retval) {
    print STDERR "ERROR: Unable to retrieve KPIs\n";
    exit($cmd_retval); 
}

exit(0);

#########################################
sub run_showkpi_cmd {
    my $opernodename = shift;
    my @cmd_output=();
    my @error_msg =();
    my @exit_code=();
    my $retval = 0;
    if ($opernodename eq $hostname) {
        ($retval, @cmd_output) = runandCaptureCommand($cmcashowkpi_cmd);
    }
    else {
        @nodelist = ();
        push(@nodelist, $opernodename);
        $cmcashowkpi_cmd = $cmcashowkpi_cmd . " -n " . $opernodename;
        ($retval, @cmd_output) = runandCaptureCommand($cmcashowkpi_cmd);
        if ("@cmd_output" =~ m/Command cmcashowkpi failed/) {
            @error_msg = split(",", "@cmd_output");
            @exit_code = split(" ", $error_msg[0]);
            $retval = $exit_code[$#exit_code];
        }
    }
    foreach my $cmdout (@cmd_output) {
        print $cmdout;
    }
    return($retval);
}

sub check_if_cluster_exists {
    my $file_val = "$SGCONF/cmclconfig";
    if (-f $file_val && -s $file_val) {
        return(1);
    }
    return (0);
}

sub parse_args {
    my $node = 0;

    foreach my $option (@ARGV) {
        if ($option eq "cmcashowkpi") {
            # do nothing
        }
        else {
            if ($node) {
                $nodeName = $option;
                $node = 0;
            }    
            else {
                if(defined($oper)) {
                    pod2usage("Multiple operation cannot be specified\n");
                    return(1);
                }
                if ($option eq "start") {
                    $oper = "start";
                }
                elsif ($option eq "stop") {
                    $oper = "stop";
                }
                else {
                    pod2usage("Invalid option specified\n");
                    return(1);
                }
            }
        }
    } 
    if (!defined($oper)) {
        pod2usage("Opertation(start|stop) must be specified\n");
        return(1);
    }
    return(0);
}

sub runandCaptureCommand {
    my ($myCmd) = @_;
    my @lines = ();
    my @output = ();
    open(CMD, "$myCmd |") or return ($?>>8 , @output);
    while (<CMD>) {
        push(@output, $_);
        last if eof(CMD);
    }
    close(CMD);
    my $exitcode = ($? >> 8);
    return($exitcode, @output);
}
sub cmexecAndCapture {
    my ($cmd) = @_;
    my $ret = 0;
    my $pid;
    my %pidNodes;
    my $node;
    foreach $node (@nodelist) {
        $cmexecFiles{$node} = "/var/tmp/cmexec.$node.$$";
        $pid = undef;
        while (!defined($pid = fork)) {
            die "ERROR: fork failed: $!" if ($! != EAGAIN);
            sleep 1;
        }
        if ($pid == 0) {
           my $final_cmd = "$SGSBIN/cmexec ". $node." ". "\"" .$cmd ."\"". " > "; #. $cmexecFiles{$node}. " 2>&1";
           $final_cmd = $final_cmd . $cmexecFiles{$node}. " 2>&1";
           exec($final_cmd) or 
                die "exec \"$cmd\" on node $node failed";
        }
        #parent
        $pidNodes{$pid} = $node;
    }

    my $runningPids = keys %pidNodes;
    while (-1 != ($pid = wait())) {
        my $stat = $?;
        if ($stat == 0) {
            #print STDOUT  "Command \"$cmd\" on node " . $pidNodes{$pid} . " completed\n";
        }
        else {
            $ret = 1;
            $node = $pidNodes{$pid};
            #my $errCmd = "cat " . $cmexecFiles{$node};
            #`$errCmd`;
        }
        $runningPids--;
    }
    die "ERROR: Failed to wait for all commands" if ($runningPids > 0);

    foreach my $node (values %pidNodes) {
        my $file = $cmexecFiles{$node};
        open(OUTFILE, "< $file") or print "ERROR: No output for node $node" and next;
        my @output = ();
        while (my $line = <OUTFILE>) {
            push(@output, $line);
        }
        $cmexecOutput{$node} = [ @output ];
        close(OUTFILE) or die "ERROR: close $cmd failed: %!";
        unlink $file;
    }
    return $ret;
}
sub non_dash_arg {

    my ($name, $val, $target) = @_;
    if ($val =~ /^-/) {
        print STDERR "ERROR: Option $name requires an argument.\n";
        $arg_errors++;
    } else {
        if (ref($target) eq "ARRAY") {
            push @{$target}, $val;
        } elsif (ref($target) eq "SCALAR") {
            # a scalar target generally indicates an option that should not be
            # used more than once, so check for that.
            if (${$target}) {
                print STDERR "ERROR: Option $name should not be supplied more than once.\n";
                pod2usage();
                $arg_errors++;
            }
            if (($name eq "f") && ($val ne "line")) {
                $arg_errors++;
            }
            ${$target} = $val;
        }
    }
}

