#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2006,2007 
# 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 

#####################################################################
#                                                                   #
# Command: mkLinuxIBstanzas                                         #
#                                                                   #
#-------------------------------------------------------------------#
#                                                                   #
#      This CSM sample script will create an adapter stanza file    #
#      for InfiniBand adapter interfaces.  It requires that either  #
#      host name resolution is set up or a file is provided with    #
#      the IB hostnames and corresponding IP addresses.  The        #
#      hostname naming convention is -                              #
#         "<short-hostname>-<interface-name>"  (ex. clstrnode1-ib0) #
#                                                                   #
#      See the mkLinuxIBstanzas.README for details on how to use    #
#      this script.                                                 #
#                                                                   #
#                                                                   #
#	Usage:                                                          #
#		mkLinuxIBstanzas [-v | -V] [-h] -z stanza-file-name         #
#          {-a | [-N nodegroups][-n nodelist][--file filename]}     #
#          [-i  hostname-file ] [Attr=value[ Attr=value...]]        #
#                                                                   #
#	Flags:                                                          #
#   -a                                                              #
#   	Process all the nodes in the CSM database.                  #
#                                                                   #
#	--file filename                                                 #
#		Specifies a file that contains a list of CSM nodes to       #
#		process. The file can contain multiple lines, and each      #
#       line can specify one or more space-separated node names.    #
#       This flag cannot be used with the -a flag.                  #
#                                                                   #
#	-h                                                              #
#		Writes the usage statement to standard output.              #
#                                                                   #
#	-i hostname-file                                                #
#		Specifies the name of a file containing a mapping of        #
#		interface hostnames and corresponding IP addresses,         #
#		(ex. "1.2.3.4    clstrnode1-ib0").                          #
#                                                                   #
#	-n node_list                                                    #
#		Specify a comma or space-separated list of node host names, #
#		IP addresses, or node ranges.                               #
#		Space-separated lists must be inside double quotes.         #
#		For information about specifying node ranges, see the       #
#		noderange man page.                                         #
#                                                                   #
#	-N nodegroups                                                   #
#		Specify the node groups to process.                         #
#                                                                   #
#	-v | -V                                                         #
#		Write the verbose messages to standard output.              #
#                                                                   #
#	-z stanza-file-name                                             #
#		Create a stanza file containing adapter configuration       #
#		information.                                                #
#                                                                   #
#                                                                   #
# Exit codes:                                                       #
#     0 - All was successful.                                       #
#     1 - An error occured.                                         #
#                                                                   #
#                                                                   #
#####################################################################
# @(#)85   1.4   src/csm/install/samplescripts/mkLinuxIBstanzas, setup, csm_rfish, rfishs001b 2/19/07 13:35:44

use strict;

BEGIN
{
    $::csmroot = $ENV{'CSM_ROOT'} ? $ENV{'CSM_ROOT'} : '/opt/csm';
    $::csmpm   = "$::csmroot/pm";
    $::csmbin  = "$::csmroot/bin";

}

$::MSGMAPPATH = $ENV{'CSM_MSGMAPS'} ? $ENV{'CSM_MSGMAPS'} : '/opt/csm/msgmaps';

use lib $::csmpm;
use Getopt::Long;
use NodeUtils;
use Socket;
use Cwd 'realpath';

#-------------------------------------------------------------------#
#   MAIN Main main section of code
#-------------------------------------------------------------------#
$::progname = "mkLinuxIBstanzas";

#---------------------
# Variables
#---------------------

$::GLOBAL_EXIT = 0;
$::NOK         = 1;
$::OK          = 0;

$::MKDIR = "/bin/mkdir";
$::CAT   = "/bin/cat";
$::GREP  = "/bin/grep";

%::ATTRS;    #  attr=val from the command line
my $attr;

# info for the /etc/sysctl.conf file
$::TCPIP_string = "    # START_TCPIP
    net.ipv4.conf.default.arp_ignore = 2
    net.ipv4.conf.default.arp_filter = 1
    net.ipv4.tcp_wmem = 32768 131072 524288
    net.ipv4.tcp_rmem = 32768 131072 524288
    net.core.wmem_max = 1048576
    net.core.rmem_max = 1048576
    # END_TCPIP
";

# info for the /etc/modprobe.conf.local file
$::modprobe_string = "    # START_MOD
    options hcad_mod nr_ports=2 port_act_time=120\
    options ib_ipoib send_queue_size=1024 recv_queue_size=1024
    # END_MOD
";

#
# CSM log directory/file
#
$::CSMLOG      = "/var/log/csm";
$::INSTALL_LOG = "/var/log/csm/install.log";

my $LOG_FILE_HANDLE = "";

#
# Append logging information to the install.log file
#
&append_logging($::INSTALL_LOG);
$::logging++;

#
#  check the command line
#
&parse_args;

#
#  Create a list of nodes and hashes
#
my @NODE_ARRAY;
if ($::NODE_LIST)
{
    @NODE_ARRAY = ($::NODE_LIST);
    undef $::NODE_LIST;
}

my ($ref_DestNode, $ref_lsnode_info, $ref_DestNodeHash) =
  NodeUtils->get_target_nodes(\@NODE_ARRAY, $::NODE_GROUP_LIST, $::FILE);

@::NODE_LIST = @$ref_DestNode;
if (!@::NODE_LIST)
{
    print "$::progname: A list of nodes was not provided.\n";
    print $LOG_FILE_HANDLE "$::progname: A list of nodes was not provided.\n";
    &usage;
    $::GLOBAL_EXIT = 1;
    exit;
}

if (-f $::STANZAFILE)
{
    print "$::progname: The file \'$::STANZAFILE\' already exists.\n";
    print $LOG_FILE_HANDLE
      "$::progname: The file \'$::STANZAFILE\' already exists.\n";
    $::GLOBAL_EXIT = 1;
    exit;
}
else
{

    #  Create the stanza file
    unless (open(STANZAFILE, ">$::STANZAFILE"))
    {
        print "$::progname: Cannot open file \'$::STANZAFILE\'.\n";
        print $LOG_FILE_HANDLE
          "$::progname: Cannot open file \'$::STANZAFILE\'.\n";
        $::GLOBAL_EXIT = 1;
        exit;
    }
}

#  Add the stanza file header
print STANZAFILE "###CSM_ADAPTERS_STANZA_FILE###--do not remove this line\n\n";

#  Add the default stanza
print STANZAFILE "default:\n";
print STANZAFILE $::TCPIP_string;
print STANZAFILE $::modprobe_string;
print STANZAFILE "\n";

if (!$::IPFILE)
{
    if ($::VERBOSE)
    {
        print
          "$::progname:  Using hostname resolution for adapter interface IP addresses.\n";
        print $LOG_FILE_HANDLE
          "$::progname:  Using hostname resolution for for adapter interface IP addresses.\n";
    }
}
else
{
    if ($::VERBOSE)
    {
        print
          "$::progname:  Using file \'$::IPFILE\' for for adapter interface IP addresses.\n";
        print $LOG_FILE_HANDLE
          "$::progname:  Using file \'$::IPFILE\' for for adapter interface IP addresses.\n";
    }
}

#  See what IB interface hostnames have been defined and
#	create a stanza for each one.
foreach my $node (@::NODE_LIST)
{
    my $i;
    my $count = 4;    # assume 4 possible IB interfaces at most
    for ($i = 0 ; $i < $count ; $i++)
    {
        $node =~ s/\..*$//;    # use short host name

        # this sample script assumes that the IB interface hostnames have
        #	been defined using the naming convention -
        #	"<short hostname>-<interface name>" ex.  "clsrnode1-ib0".
        my $intname = $node . "-ib" . $i;

        #  either use the hostname mapping file passed in on the command line,
        # 	if provided,or use standard name resolution to check for IB
        #	interfaces.
        if (-f $::IPFILE)
        {
            my $nodeip;
            if ($::VERBOSE)
            {
                print
                  "$::progname:  Using file \'$::IPFILE\' for IP addresses.\n";
            }
            print $LOG_FILE_HANDLE
              "$::progname:  Using file \'$::IPFILE\' for IP addresses.\n";

            #  see if this interface name is in the file
            my $line = `$::CAT $::IPFILE | $::GREP $intname`;
            if ($line)
            {

                # if so - get the IP address
                ($nodeip, undef) = split(' ', $line);

                #  create the stanza for this interface
                if ($::VERBOSE)
                {
                    print
                      "$::progname:  Creating stanza for node \'$node\' interface \'ib$i\'.\n";
                    print $LOG_FILE_HANDLE
                      "$::progname:  Creating stanza for node \'$node\' interface \'ib$i\'.\n";
                }
                &doIBstanza($intname, $nodeip);
            }
            else
            {

                # otherwise just go to the next interface name
                next;
            }
        }
        else    # check name resolution
        {

            #  get the IP address for this hostname - if any
            my ($hostname, undef, undef, undef, undef) =
              gethostbyname($intname);
            if (length($hostname))
            {
                my $nodeip;
                $nodeip = Socket::inet_ntoa(Socket::inet_aton($hostname));

                #  create the stanza for this interface
                if ($::VERBOSE)
                {
                    print
                      "$::progname:  Creating stanza for node \'$node\' interface \'ib$i\'.\n";
                    print $LOG_FILE_HANDLE
                      "$::progname:  Creating stanza for node \'$node\' interface \'ib$i\'.\n";
                }
                &doIBstanza($intname, $nodeip);

            }
            else
            {

                # otherwise just go to the next interface name
                next;
            }
        }
    }
}
print "Successfully created stanza file \'$::STANZAFILE\'.\n";
print $LOG_FILE_HANDLE
  "$::progname:  Successfully created stanza file \'$::STANZAFILE\'.\n";

#
# finish up and exit
#
END
{

    if ($::logging)
    {
        &stop_logging();
    }

    #Determine exit code
    if ($::GLOBAL_EXIT > $?)
    {
        $? = $::GLOBAL_EXIT;
    }
}

exit;    # end of Main

#--------------- subroutines ----------------------------------

#-------------------------------------------------------------------------#
#    doIBstanza  - create an IB stanza                                    #
#                                                                         #
#--------------------------------------#----------------------------------#
sub doIBstanza
{
    my ($name, $ip) = @_;    # the interface name and IP address
    my ($a, $b, $c, $d);
    my ($node, $intname);
    my $ipbase;

    #  split up the IP address
    ($a, $b, $c, $d) = split /\./, $ip;

    # get the node name and interface name from
    #	the interface hostname (ex. node1-ib0)
    ($node, $intname) = split /\-/, $name;

    # this string is used to construct the default values for
    #	NETWORK and BROADCAST attributes
    $ipbase = "$a.$b.$c.";

	# get long hostname for stanza file
	my ($host, $junk) = NetworkUtils->getHost($node);

    # start writing the stanza to the file
    print STANZAFILE "$host:";
    print STANZAFILE "
    machine_type=secondary
    adapter_type=iba
    interface_name=$intname\n";

    print STANZAFILE "    # START_IFCFG\n";

    print STANZAFILE "    IPADDR='$ip'\n";

    # the rest of the attributes can be provided on the command line.
    #	If not, defaults will be set.  To provide a value on the command
    #	line use the "attr=value"format-
    #	"<interface-name>-<attribute-name>=<netmask>"
    #	ex. "ib2-NETMASK=255.255.255.196".
    #	This script assumes that this value would apply to all the
    #	interfaces with the same names that are defined on the various nodes.
    #	So in this example we assume that all the ib2 interfaces are on the
    #	same subnet.

    $attr = "$intname-BOOTPROTO";
    if ($::ATTRS{$attr})
    {
        print STANZAFILE "    BOOTPROTO=\'$::ATTRS{$attr}\n";
    }
    else
    {

        #  default -
        print STANZAFILE "    BOOTPROTO='static'\n";
    }

    $attr = "$intname-STARTMODE";
    if ($::ATTRS{$attr})
    {
        print STANZAFILE "    STARTMODE=\'$::ATTRS{$attr}\'\n";
    }
    else
    {

        #  default -
        print STANZAFILE "    STARTMODE='onboot'\n";
    }

    $attr = "$intname-NETMASK";
    if ($::ATTRS{$attr})
    {
        print STANZAFILE "    NETMASK=\'$::ATTRS{$attr}\'\n";
    }
    else
    {

        #  default -
        print STANZAFILE "    NETMASK=\'255.255.255.0\'\n";
    }

    $attr = "$intname-BROADCAST";
    if ($::ATTRS{$attr})
    {
        print STANZAFILE "    BROADCAST=\'$::ATTRS{$attr}\'\n";
    }
    else
    {

        #  default - replace the last number of the IP address with 255
        my $bc = $ipbase . "255";
        print STANZAFILE "    BROADCAST='$bc'\n";
    }

    $attr = "$intname-NETWORK";
    if ($::ATTRS{$attr})
    {
        print STANZAFILE "    NETWORK=\'$::ATTRS{$attr}\'\n";
    }
    else
    {

        #  default - replace the last number of the IP address with 0
        my $nw = $ipbase . "0";
        print STANZAFILE "    NETWORK='$nw'\n";
    }

    print STANZAFILE "    # END_IFCFG\n\n";

    return $::OK;
}

#-------------------------------------------------------------------------#
# usage - 												                  #
#--------------------------------------#----------------------------------#
sub usage
{
    print
      "Usage: \n    mkLinuxIBstanzas [-v | -V] [-h] -z stanza-file-name\n          {-a | [-N nodegroups] [-n nodelist] [--file filename]}\n          [-i hostname-file] [Attr=value[ Attr=value...]]\n";
    return;
}

#-------------------------------------------------------------------------#
# parse_args - Parse the command line for options and operands.           #
#--------------------------------------#----------------------------------#
sub parse_args
{

    my @command_line = ();
    @command_line   = @ARGV;
    $::command_line = $0 . " " . join(" ", @command_line);

    #  Checks case in GetOptions
    $Getopt::Long::ignorecase = 0;

    #  Allows opts to be grouped (e.g. -avx)
    Getopt::Long::Configure("bundling");

    if (
        !GetOptions(
                    'a'      => \$::ALL,
                    'h'      => \$::HELP,
                    'i=s'    => \$::IPFILE,
                    'n=s'    => \$::NODE_LIST,
                    'N=s'    => \$::NODE_GROUP_LIST,
                    'z=s'    => \$::STANZAFILE,
                    'file=s' => \$::FILE,
                    'v'      => \$::VERBOSE,
                    'V'      => \$::VERBOSE,
        )
      )
    {
        &usage;
        exit 1;
    }

    if ($::HELP)
    {
        &usage;
        exit 0;
    }

    if (!$::STANZAFILE)
    {

        # use default name
        $::STANZAFILE = "IBstanzafile.$$";
    }

    if (!$::NODE_LIST && !$::NODE_GROUP_LIST && !$::FILE && !$::ALL)
    {
        print "$::progname: Target nodes were not provided.\n";
        print $LOG_FILE_HANDLE
          "$::progname: Target nodes were not provided.\n";
        &usage;
        exit 1;
    }

    if (($::NODE_LIST || $::NODE_GROUP_LIST || $::FILE) && ($::ALL))
    {
        print "$::progname: Cannot use -a with -n, -N, or --file.\n";
        print $LOG_FILE_HANDLE
          "$::progname: Cannot use -a with -n, -N, or --file.\n";
        &usage;
        exit 1;
    }

    # Handle attr=value arguments
    if (scalar(@ARGV) > 0)
    {
        foreach my $a (@ARGV)
        {

            # Note:  we allow a null value
            my ($attr, $value) = $a =~ /^\s*(\S+?)\s*=\s*(\S*.*)$/;

            if (!$attr)
            {
                print
                  "$::progname: The operands do not have the correct format.\n";
                print $LOG_FILE_HANDLE
                  "$::progname: The operands do not have the correct format.\n";
                $::GLOBAL_EXIT = 1;
                exit;
            }

            $::ATTRS{$attr} = $value;

        }
    }

    return $::OK;
}

#-------------------------------------------------------------------------#
#                                                                         #
#    append_logging                                                       #
#                                                                         #
#         Append logging messages to a logfile. Return the log file       #
#           handle so it can be used to close the file when done logging. #
#                                                                         #
#----------------------------------------#--------------------------------#
sub append_logging
{
    my ($logfile, $stat) = @_;
    my ($cmd,     $rc);

    # create the log directory if it's not already there
    if (!-d $::CSMLOG)
    {
        $cmd = "$::MKDIR -m 644 -p $::CSMLOG";
        $rc  = system("$cmd");
        if ($rc >> 8)
        {
            print
              "$::progname: 2653-158 Could not create \"$::CSMLOG\" directory.\n";
            return ($::NOK);
        }
    }

    #
    #  get log file ready
    #
    if (!-e $logfile)
    {

        #  create the log file if not already there
        # open the log file
        unless (open(LOGFILE, ">$logfile"))
        {

            # Cannot open file
            print
              "$::progname: 2653-141 Cannot open file \"$logfile\" for writing.\n";
            return $::NOK;
        }
    }
    else
    {

        # it's there so just append
        unless (open(LOGFILE, ">>$logfile"))
        {
            print "$::progname: 2653-036 Could not update  \"$logfile\".\n";
            return $::NOK;
        }
    }

    $LOG_FILE_HANDLE = \*LOGFILE;

    if ((defined $stat) && ($stat == 1))
    {
        return ($LOG_FILE_HANDLE);
    }

    # Print the date to the top of the logfile
    my $sdate = `/bin/date`;
    chomp $sdate;
    if ($::VERBOSE)
    {
        print "Output log is being written to \"$logfile\".\n";
    }

    print $LOG_FILE_HANDLE
      "---------------------------------------------------------------------\n";
    print $LOG_FILE_HANDLE "$::progname: Logging started $sdate.\n";
    print $LOG_FILE_HANDLE
      "---------------------------------------------------------------------\n";

    return ($LOG_FILE_HANDLE);
}

#-------------------------------------------------------------------------#
#                                                                         #
#    stop_logging                                                         #
#                                                                         #
#                  Turn off message logging.  (Expects to have a file     #
#                   handle passed in. )                                   #
#                                                                         #
#--------------------------------------#----------------------------------#

sub stop_logging
{

    # Print the date at the bottom of the logfile
    my $sdate = `/bin/date`;
    chomp $sdate;
    print $LOG_FILE_HANDLE
      "---------------------------------------------------------------------\n";
    print $LOG_FILE_HANDLE "$::progname: Logging stopped $sdate.\n";
    print $LOG_FILE_HANDLE
      "---------------------------------------------------------------------\n";

    close($LOG_FILE_HANDLE);
    $LOG_FILE_HANDLE = undef;

    return $::OK;
}

