#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
# bos720 src/bos/usr/bin/cdat/cdat-discover-nodes.pl 1.8 
#  
# Licensed Materials - Property of IBM 
#  
# COPYRIGHT International Business Machines Corp. 2010,2011 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 
# @(#)24    1.8  src/bos/usr/bin/cdat/cdat-discover-nodes.pl, cdat, bos720 7/15/11 04:33:13
use warnings;
use strict;
use Getopt::Long;
use File::Copy;
use cdat;
use messages;

#
# Constants.
#
# This is the command that is run on HMC or IVM to list LPARs.
use constant DISCOVER_LPARS_CMD =>
    '(lssyscfg -r sys -F name |'.
     'while read system; do '.
    	 'lssyscfg -r lpar -m $system -F lpar_env,name |'.
    	 'while read lpar; do '.
	     'echo "$lpar" | grep -q -e ^aixlinux -e ^vioserver && '.
    	     'echo "$system,$lpar" || '.
	     'echo "$system,error,$lpar";'.
    	 'done;'.
     'done) 2>&1';

# This is the command that is run on LPARs to list WPARs.
use constant DISCOVER_WPARS_CMD =>
    '/usr/sbin/lswpar -cq -a Hostname | /usr/bin/sort';

#
# Globals.
#
my @file = ();

######################################################################
# Function:	usage
# Purpose:	Display usage.
# Tasks:	Print usage and exit.
# Input:	None
# Output:	None
######################################################################
sub usage
{
    printf(STDERR catgets(MSG_CDAT_DISCOVER_USAGE,
        "Usage: cdat discover-nodes -h\n".
	"       cdat discover-nodes [-a|-w] [-f File] -n Type:[User@]Node ...\n"));
    exit(1);
}

######################################################################
# Function:	discover_lpars
# Purpose:	Discover LPARs managed by a given host (HMC or IVM).
# Tasks:	Log onto the host and list the LPARs.
#		Update global array @file with the LPARs.
# Input:	Host (type, username, hostname)
# Output:	0 on success, 1 otherwise
######################################################################
sub discover_lpars
{
    my ($type, $user, $name) = @_;

    # Execute the discover command on the HMC/IVM.
    # NB: it may ask for a password.
    my @lines = cdat::remote_cmd($user, $name, DISCOVER_LPARS_CMD,
	$cdat::REMOTE_PROBE | $cdat::REMOTE_AUTO);
    my $status = $?;
    if ($status != 0 || !defined($lines[0])) {
        printf(STDERR catgets(MSG_CANNOT_DISCOVER_SKIP,
	    "Cannot discover nodes from %s, skipping.\n"), $name);
        return 1;	# skip this HMC/IVM
    }

    if ($type eq 'VIOS' && grep(/^\[VIOSE/, @lines)) {
	printf(STDERR catgets(MSG_CANNOT_DISCOVER_SKIP,
	    "Cannot discover nodes from %s, skipping.\n"), $name);
	return 1;	# skip this VIOS
    }

    if ($type eq 'HMC' && !grep(/^HMC:$name\n/, @file)) {
	# Add the HMC name itself
	push(@file, "HMC:$name\n");
    }

    # Sort LPARs by managed system, type and name. Unfortunately, ioscli
    # does not have a 'sort' command (while HMCs have one) so we have to
    # sort things by ourselves.
    @lines = sort @lines;

    my $prev_system;
    foreach (@lines) {
    	chomp;
        my ($system, $type, $name) = split(/,/, $_, 3);
        my $qualifier;

	# make sure we have all three fields
	next if (!defined($name));

	if ($type eq "vioserver") {
	    $qualifier = "VIOS:$name";
	} elsif ($type eq "aixlinux") {
	    $qualifier = "LPAR:$name";
	} elsif ($type eq "error") {
	    push(@file, "# Could not retrieve LPARs of managed system $system\n");
	    push(@file, "# $name\n");
	    next;
	} else {
	    # skip unknown types
	    next;
	}
        # Skip if node is already in the file
        next if (grep(/^$qualifier\n/, @file));

	if (!defined($prev_system) || ($system ne $prev_system)) {
            push(@file, "# LPARs of managed system $system\n");
	    $prev_system = $system;
	}
	# Add node to the file
	push(@file, "$qualifier\n");
    }

    return 0;
}

######################################################################
# Function:	discover_wpars
# Purpose:	Discover WPARs managed by a given LPAR.
# Tasks:	Log onto the host and list the WPARs.
#		Update global array @file with the WPARs.
# Input:	Host (type, username, hostname)
# Output:	None
######################################################################
sub discover_wpars
{
    my ($type, $user, $name) = @_;

    # Execute the discover command on the LPAR.
    # NB: it may ask for a password.
    my @lines = cdat::remote_cmd($user, $name, DISCOVER_WPARS_CMD,
	$cdat::REMOTE_PROBE | $cdat::REMOTE_AUTO);
    my $status = $?;
    if ($status != 0 || !defined($lines[0])) {
        printf(STDERR catgets(MSG_CANNOT_DISCOVER_SKIP,
	    "Cannot discover nodes from %s, skipping.\n"), $name);
        return 1;	# skip this LPAR
    }

    if (!grep(/^LPAR:$name\n/, @file)) {
	# Add the LPAR name itself
	push(@file, "LPAR:$name\n");
    }

    # NB: WPARs are already sorted by name
    my $hdr = 0;
    foreach (@lines) {
        chomp;
	my $wpar = $_;

	# Skip if WPAR is already in the file
	next if (grep(/^LPAR:$wpar\n/, @file));

	if ($hdr == 0) {
	    @file = (@file, "# WPARs of LPAR $name\n");
	    $hdr = 1;
	}
	# Add WPAR to the file
	push(@file, "LPAR:$wpar\n");
    }

    return 0;
}

######################################################################
# Function:	main
# Purpose:	Entry point of the discover-nodes subcommand.
# Tasks:	Parse command line and call discover_nodes for each
#		specified node.
# Input:	None
# Output:	None
######################################################################
sub main
{
    my ($rc, $filename, $overwrite, $append);
    my @rawnodes;

    # Parse command line options
    Getopt::Long::Configure('bundling', 'no_ignore_case');
    $rc = GetOptions(
        'h'   => \&usage,
        'n=s' => \@rawnodes,
        'f=s' => \$filename,
        'w'   => \$overwrite,
        'a'   => \$append
    );
    if (!$rc || @ARGV != 0) {
        usage();
    }

    # check that -a and -w are not both defined
    if (defined($overwrite) && defined($append)) {
	usage();
    }

    cdat::switch_user();

    my %nodes = cdat::read_nodes_from_array(@rawnodes);
    if (!%nodes) {
        printf(STDERR catgets(MSG_NO_NODES_TO_CONNECT,
	    "No nodes to connect to.\n"));
	exit(1);
    }

    if (!defined($filename)) {
    	# no filename was specified, use default
        my $path = cdat::odm_get_path();
        if (!defined($path)) {
            printf(STDERR catgets(MSG_DIR_NOT_DEFINED,
	    	"The cdat directory path is not defined.\n".
		"Please, run 'cdat init' first.\n"));
            exit(1);
        }
        $filename = "$path/nodes.txt";
    }

    if ( -e $filename && ! -z _ ) {
        #
        # A file with that name already exists and is not empty,
	# decide what to do.
	#
        if (!defined($append) && !defined($overwrite)) {
            # neither -a nor -w were specified, ask user
	    printf(catgets(MSG_FILE_ALREADY_EXISTS,
	        "File %s already exists, replace it?"), $filename);
	    if (cdat::yes_no(0)) {
	        $overwrite = 1;
	    } else {
	        $append = 1;
	    }
        }

        if (defined($append)) {
            # Read existing file content into memory
	    $rc = open(FILE, "<$filename");
	    if (!$rc) {
	        printf(STDERR catgets(MSG_CANNOT_OPEN_FOR_READING,
		    "Cannot open %s for reading.\n"), $filename);
	        exit(1);
	    }
	    @file = <FILE>;
	    close(FILE);
        } else {
            # Make a copy of existing file and remove original
            $rc = move("$filename", "$filename.bak");
            if (!$rc) {
                printf(STDERR catgets(MSG_CANNOT_BACKUP,
		    "Cannot backup %s.\n"), $filename);
                exit(1);
            }
        }
    }

    # Discover nodes managed by each specified host
    my $status = 0;
    while (my ($name, $node) = each(%nodes)) {
	my $type = $node->{type};

	# We can only discover nodes from HMCs, IVMs or LPARs for now.
	if ($type ne 'HMC' && $type ne 'VIOS' && $type ne 'LPAR') {
	    printf(STDERR catgets(MSG_SKIPPING_NODE,
		"Unsupported node type %s, skipping %s.\n"), $type, $name);
	    next;
	}

	my $user;
	if (!defined($node->{user})) {
	    # Choose default user
	    if ($type eq 'HMC') {
	        $user = 'hscroot';
	    } elsif ($type eq 'VIOS') {
	        $user = 'padmin';
	    } else {	# LPAR
		$user = 'root';
	    }
	} else {
	    $user = $node->{user};
	}

	printf(catgets(MSG_DISCOVERING_NODES,
	    "Discovering nodes managed by %s@%s...\n"), $user, $name);

	my $ret = 0;
	if ($type eq 'HMC' || $type eq 'VIOS') {
	    $ret = discover_lpars($type, $user, $name);
	} else {	# LPAR
	    $ret = discover_wpars($type, $user, $name);
	}
	if ($ret == 0) {
	    printf(catgets(MSG_DONE, "Done.\n"));
	} else {
	    $status = 2;
	}
    }

    $rc = open(FILE, ">$filename");
    if (!$rc) {
        printf(STDERR catgets(MSG_CANNOT_OPEN_FOR_WRITING,
	    "Cannot open %s for writing.\n"), $filename);
        exit(3);
    }
    print(FILE @file);
    close(FILE);

    exit($status);
}

main;
