# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT_plugin::configfpc;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use strict;
use lib "$::XCATROOT/lib/perl";
use xCAT::Utils;
use xCAT::MsgUtils;
use xCAT::SvrUtils;
use Sys::Hostname;
use xCAT::Table;
use xCAT::TableUtils;
use xCAT::NetworkUtils;

#use Data::Dumper;
use xCAT::MacMap;
use Socket;
use Net::Ping;

##########################################################################
## Command handler method from tables
###########################################################################
sub handled_commands {
    return {
        configfpc => "configfpc",
    };
}


#sub preprocess_request {

#   set management node as server for all requests
#   any requests sent to service node need to get
#   get sent up to the MN

#my $req = shift;
##if already preprocessed, go straight to request
#if (   (defined($req->{_xcatpreprocessed}))
#&& ($req->{_xcatpreprocessed}->[0] == 1))
#{
#return [$req];
#}
#
#$req->{_xcatdest} = xCAT::TableUtils->get_site_Master();
#return [$req];
#}



sub process_request {
    my $request  = shift;
    my $callback = shift;
    my $subreq   = shift;

    #my $subreq = $request->{command};

    $::CALLBACK = $callback;

    if   ($request && $request->{arg}) { @ARGV = @{ $request->{arg} }; }
    else                               { @ARGV = (); }

    Getopt::Long::Configure("bundling", "no_ignore_case", "no_pass_through");
    my $getopt_success = Getopt::Long::GetOptions(
        'help|h|?'  => \$::opt_h,
        'i|I=s'     => \$::opt_I,
        'ip|ip=s'   => \$::opt_IP,
        'verbose|V' => \$::opt_V,
    );

    # Option -h for Help
    if (defined($::opt_h) || (!$getopt_success)) {
        &configfpc_usage;
        return 0;
    }

    if ((!$::opt_I)) {    # missing required option - msg and return
        my $rsp;
        push @{ $rsp->{data} }, "Missing required option -i <adapter_interface> \n";
        xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
        &configfpc_usage;
        return 0;
    }

    # Option -V for verbose output
    if (defined($::opt_V)) {
        $::VERBOSE = $::opt_V;
    }

    # Option -i for kit component attributes
    if (defined($::opt_I)) {
        $::interface = $::opt_I;
    }

    my $command       = $request->{command}->[0];
    my $localhostname = hostname();

    if ($command eq "configfpc")
    {
        my $rc;
        $rc = configfpc($request, $callback, $subreq);
    }
    else
    {
        my $rsp;
        push @{ $rsp->{data} }, "$localhostname: Unsupported command: $command";
        xCAT::MsgUtils->message("I", $rsp, $callback);
        return 1;
    }

    return 0;
}

sub configfpc_usage {
    my $rsp;
    push @{ $rsp->{data} },
"\nUsage: configfpc - Configure the NeXtScale FPCs.i This command requires the -i option to give specify which network adapter to use to look for the FPCs.\n";
    push @{ $rsp->{data} },
      "  configfpc -i interface [--ip default ip address]\n ";
    push @{ $rsp->{data} },
      "  configfpc [-V|--verbose] -i interface [--ip default ip address] \n ";
    push @{ $rsp->{data} }, "  configfpc [-h|--help|-?] \n";
    xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
    return 0;
}

#
# Main process subroutine
#
###########################################################################
# This routine will look for NeXtWcale Fan Power Controllers (FPC) that have
# a default IP address of 192.168.0.100
# It will take in a different default IP address, if you input --ip <new default ip>
#
# For each FPC found the code will
# 1 - ping the default IP address to get the default IP and MAC in the arp table
# 2 - use arp to get the MAC address
# 3 - lookup the MAC address
# ------ check for the MAC on the switch adn save the port number
# ------ lookup the node with that switch port number
# 4 - get the IP address for the node name found
# 5 - determine the assocaited netmask and gateway for this node IP
# 6 - use rspconfig (IPMI) to set the netmask, gateway, and IP address
# 7 - check to make sure that the new FPC IP address is responding
# 8 - remove the default FPC IP from the arp table
# 9 - use ping to determine if there is another default FPC IP and if so start this process again
###########################################################################
sub configfpc {
    my $request  = shift;
    my $callback = shift;
    my $subreq   = shift;

    $::CALLBACK = $callback;

    # Use default userid and passwd
    my $ipmiuser = 'USERID';
    my $ipmipass = 'PASSW0RD';
    my $fpcip    = '192.168.0.100';
    if ($::opt_IP) {    # override with --ip input
        $fpcip = $::opt_IP;
    }

    # Build route,  if defaultip is 192.168.0.100, then route is 192.168.0.101/16
    my ($a1, $a2, $a3, $a4) = split(/\./, $fpcip);
    my $a4    = $a4 + 1;
    my $a5    = "\/16";
    my $route = join(".", $a1, $a2, $a3, $a4);
    my $route = $route . $a5;


    if ($::VERBOSE) {
        my $rsp;
        push @{ $rsp->{data} }, "Default IP address is $fpcip \n";
        xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
    }
    if ($::VERBOSE) {
        my $rsp;
        push @{ $rsp->{data} }, "Default route address is $route \n";
        xCAT::MsgUtils->message("I", $rsp, $::CALLBACK);
    }
    my $defnode = 'deffpc';

    # This is the default FPC IP that we are looking for
    my $foundfpc = 0;

    # Setup routing to on the network:w
    if ($::VERBOSE) {
        my $rsp;
        push @{ $rsp->{data} }, "Running ip addr add dev $::interface $route";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }

    my $setroute = `ip addr add dev $::interface $route`;

    #
    # check for an FPC - this ping will also add the FPC IP and MAC  to the ARP table
    #
    my $res = `LANG=C ping -c 1 -w 5 $fpcip`;
    if ($res =~ /100% packet loss/g) {
        $foundfpc = 0;

        my $rsp;
        push @{ $rsp->{data} }, "No default $fpcip IP addresses found";
        xCAT::MsgUtils->message("I", $rsp, $callback);
        exit;    # EXIT if we find no more default IP addresses on the network
    }
    else {
        if ($::VERBOSE) {
            my $rsp;
            push @{ $rsp->{data} }, "Found $fpcip address";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        }
        $foundfpc = 1;
    }


    my $addnode = &add_node($defnode, $fpcip, $callback);

    #
    # Main loop - check to see if we found an FPC and continue to set the FPC infomration and look for the next one
    #
    while ($foundfpc) {

        # Process the default FPC IP to find the node associated with this FPC MAC
        my ($node, $fpcmac) = &get_node($callback);

        # Found the Node and MAC associated with this MAC - continue to setup this FPC
        if ($node) {

            # get the network settings for this node
            my ($netmask, $gateway, $newfpcip) = &get_network_parms($node, $callback);

            # Change the FPC network netmask, gateway, and ip
            &set_FPC_network_parms($defnode, $netmask, $gateway, $newfpcip, $callback, $subreq);

            # message changed network settings
            my $rsp;
            push @{ $rsp->{data} }, "Configured FPC with MAC $fpcmac as $node ($newfpcip)";
            xCAT::MsgUtils->message("I", $rsp, $callback);

            # Validate that new IP is working - Use ping to check if the new IP has been set
            my $p            = Net::Ping->new();
            my $ping_success = 1;
            while ($ping_success) {
                if ($p->ping($newfpcip)) {
                    my $rsp;
                    push @{ $rsp->{data} }, "Verified the FPC with MAC $fpcmac is responding to the new IP $newfpcip as node $node";
                    xCAT::MsgUtils->message("I", $rsp, $callback);
                    $ping_success = 0;
                }
                else {
                    if ($::VERBOSE) {
                        my $rsp;
                        push @{ $rsp->{data} }, "ping to $newfpcip is unsuccessful. Retrying ";
                        xCAT::MsgUtils->message("I", $rsp, $callback);
                    }
                }
            }
            $p->close();

            # The Node associated with this MAC was not found - print an infomrational message and continue
        } else {
            my $rsp;
            push @{ $rsp->{data} }, "No FPC found that is associated with MAC address $fpcmac.\nCheck to see if the switch and switch table contain the information needed to locate this FPC MAC";
            xCAT::MsgUtils->message("E", $rsp, $callback);
            $foundfpc = 0;
        }

        #
        # Delete this FPC default IP Arp entry to get ready to look for another defautl FPC
        #
        if ($::VERBOSE) {
            my $rsp;
            push @{ $rsp->{data} }, "Removing default IP $fpcip from the arp table";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        }
        my $arpout = `arp -d $fpcip`;

        if (($foundfpc == 1)) {    # if the last FPC was found and processed

            # check for another FPC
            $res = `LANG=C ping -c 1 -w 5 $fpcip 2>&1`;
            if (($res =~ /100% packet loss/g) && ($foundfpc == 1)) {
                my $rsp;
                push @{ $rsp->{data} }, "There are no more  default IP address to process";
                xCAT::MsgUtils->message("I", $rsp, $callback);
                $foundfpc = 0;
            }
            else {
                $foundfpc = 1;
            }
        }
    }

    #
    # Cleanup on the way out - Delete route and remove the deffpc node definition
    #
    # Delete routing to from the network
    if ($::VERBOSE) {
        my $rsp;
        push @{ $rsp->{data} }, "Running ip addr del dev $::interface $route";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }
    my $setroute = `ip addr del dev $::interface $route`;

    if ($::VERBOSE) {
        my $rsp;
        push @{ $rsp->{data} }, "Removing default FPC node definition $defnode";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }


    # Remove the defnode node entry
    my $out = xCAT::Utils->runxcmd({ command => ["noderm"], node => ["$defnode"] }, $subreq, 0, 2);

    return 1;
}

#
# The get_network_parms subroutine
# takes the node name and gets the IP address for this node
# and collects the netmask and gateway and returns netmask, gateway, and IP address
#
sub get_network_parms {

    my $node     = shift;
    my $callback = shift;

    # Get the new ip address for this FPC
    my $newfpc = `getent hosts $node`;
    my ($newfpcip, $junk) = split(/\s/, $newfpc);

    # collect gateway and netmask
    my $ip = $newfpcip;
    my $gateway;
    my $netmask;
    if (inet_aton($ip)) {
        $ip = inet_ntoa(inet_aton($ip));
    } else {
        my $rsp;
        push @{ $rsp->{data} }, "Unable to resolve $ip";
        xCAT::MsgUtils->message("I", $rsp, $callback);
        return undef;
    }
    my $nettab = xCAT::Table->new('networks');
    unless ($nettab) { return undef }
    my @nets = $nettab->getAllAttribs('net', 'mask', 'gateway');
    foreach (@nets) {
        my $net  = $_->{'net'};
        my $mask = $_->{'mask'};
        my $gw   = $_->{'gateway'};
        $ip =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/;
        my $ipnum = ($1 << 24) + ($2 << 16) + ($3 << 8) + $4;
        $mask =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/;
        my $masknum = ($1 << 24) + ($2 << 16) + ($3 << 8) + $4;
        $net =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/ or next; #next if ipv6, TODO: IPv6 support
        my $netnum = ($1 << 24) + ($2 << 16) + ($3 << 8) + $4;

        if ($gw eq '<xcatmaster>') {
            my @gwd = xCAT::NetworkUtils->my_ip_facing($ip);
            unless ($gwd[0]) { $gw = $gwd[1]; }
        }
        if (($ipnum & $masknum) == $netnum) {
            $netmask = $mask;
            $gateway = $gw;
        }
    }
    return ($netmask, $gateway, $newfpcip);
}

#
# The set_FPC_network_parms subroutine
# uses rspconfig to set the netmask, gateway, and new ip address for this FPC
#
sub set_FPC_network_parms {

    my $defnode  = shift;
    my $netmask  = shift;
    my $gateway  = shift;
    my $newfpcip = shift;
    my $callback = shift;
    my $request  = shift;
    my $error;

    # Proceed with changing the FPC network parameters.
    # Set FPC Netmask
    if ($::VERBOSE) {
        my $rsp;
        push @{ $rsp->{data} }, "Use rspconfig to set the FPC netmask $netmask";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }
    my $netmaskout = xCAT::Utils->runxcmd(
        {
            command => ["rspconfig"],
            node    => ["$defnode"],
            arg     => ["netmask=$netmask"],
        },
        $request, 0, 1);
    if ($::RUNCMD_RC != 0) {
        my $rsp;
        push @{ $rsp->{data} }, "Could not change nemask $netmask";
        xCAT::MsgUtils->message("I", $rsp, $callback);
        $error++;
    }

    # Set FPC gateway
    if ($::VERBOSE) {
        my $rsp;
        push @{ $rsp->{data} }, "Use rspconfig to set the FPC gateway $gateway";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }
    my $gatewayout = xCAT::Utils->runxcmd(
        {
            command => ["rspconfig"],
            node    => ["$defnode"],
            arg     => ["gateway=$gateway"],
        },
        $request, 0, 1);
    if ($::RUNCMD_RC != 0) {
        my $rsp;
        push @{ $rsp->{data} }, "Could not change gateway $gateway";
        xCAT::MsgUtils->message("I", $rsp, $callback);
        $error++;
    }

    # Set FPC Ip address
    if ($::VERBOSE) {
        my $rsp;
        push @{ $rsp->{data} }, "Use rspconfig to set the FPC IP address $newfpcip";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }
    my $ipout = xCAT::Utils->runxcmd(
        {
            command => ["rspconfig"],
            node    => ["$defnode"],
            arg     => ["ip=$newfpcip"],
        },
        $request, 0, 1);
    if ($::RUNCMD_RC != 0) {
        my $rsp;
        push @{ $rsp->{data} }, "Could not change ip address $newfpcip on default FPC";
        xCAT::MsgUtils->message("S", $rsp, $callback);
        $error++;
    }
    return 1;
}

#
# This subroutine
# 1) gets the MAC from the arp table
# 2) uses Macmap to find the node associated with this MAC
# 3) returns the node and MAC
sub get_node {
    my $callback = shift;

    my $fpcip = '192.168.0.100';    # default if not entered on CLI
    if ($::opt_IP) {                # override with --ip input
        $fpcip = $::opt_IP;
    }


    # get the FPC from the arp table
    my $arpout = `arp -a | grep $fpcip`;

    # format of arp command is: feihu-fpc (10.1.147.170) at 6c:ae:8b:08:20:35 [ether] on eth0
    # extract the MAC address
    my ($junk1, $junk2, $junk3, $fpcmac, $junk4, $junk5, $junk6) = split(" ", $arpout);

    # set the FPC MAC as static for the arp table
    my $arpout = `arp -s $fpcip $fpcmac`;

    # Print a message that this MAC has been found
    my $rsp;
    push @{ $rsp->{data} }, "Found IP $fpcip and MAC $fpcmac";
    xCAT::MsgUtils->message("I", $rsp, $callback);

    # Usee find_mac to 1) look for which switch port contains this MAC address
    # and 2) look in the xcat DB to find the node associated with the switch port this MAC was found in
    my $macmap = xCAT::MacMap->new();
    my $node   = '';
    $node = $macmap->find_mac($fpcmac, 0);

    # verbose
    if ($::VERBOSE) {
        my $rsp;
        push @{ $rsp->{data} }, "Mapped MAC $fpcmac to node $node";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }

    return ($node, $fpcmac);
}

#
# This subroutine adds the deffpc node entry for use by rspconfig
#
sub add_node {
    my $defnode  = shift;
    my $fpcip    = shift;
    my $callback = shift;

    # add this node entry
    # Object name: feihu-fpc
    #     bmc=feihu-fpc		(Table:ipmi - Key:node - Column:bmc)
    #     bmcpassword=PASSW0RD	(Table:ipmi - Key:node - Column:password)
    #     bmcusername=USERID	(Table:ipmi - Key:node - Column:username)
    #     cons=ipmi			(Table:nodehm - Key:node - Column:cons)
    #     groups=fpc		(Table:nodelist - Key:node - Column:groups)
    #     mgt=ipmi			(Table:nodehm - Key:node - Column:mgt)
    #

    if ($::VERBOSE) {
        my $rsp;
        push @{ $rsp->{data} }, "Creating default FPC node deffpc with IP $fpcip for later use with rspconfig";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }

    my $nodelisttab = xCAT::Table->new('nodelist', -create => 1);
    $nodelisttab->setNodeAttribs($defnode, { groups => 'defaultfpc' });
    my $nodehmtab = xCAT::Table->new('nodehm', -create => 1);
    $nodehmtab->setNodeAttribs($defnode, { mgt => 'ipmi' });
    my $ipmitab = xCAT::Table->new('ipmi', -create => 1);
    $ipmitab->setNodeAttribs($defnode, { bmc => $fpcip, username => 'USERID', password => 'PASSW0RD' });
    return 0;
}

1;