# IBM(c) 2010 EPL license http://www.eclipse.org/legal/epl-v10.html
#-------------------------------------------------------

=head1
  xCAT plugin package to handle xdsh

   Supported command:
         nodenetconn
         ipforward (internal command)

=cut

#-------------------------------------------------------
package xCAT_plugin::route;
BEGIN
{
  $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use strict;
use xCAT::Table;
use xCAT::Utils;
use xCAT::NetworkUtils;
use xCAT::MsgUtils;
use Getopt::Long;
use xCAT::NodeRange;
use Data::Dumper;
use xCAT::NodeRange;

1;

#-------------------------------------------------------

=head3  handled_commands

Return list of commands handled by this plugin

=cut

#-------------------------------------------------------

sub handled_commands
{
    return {
            makeroutes => "route",
            ipforward => "route"
           };
}


#-------------------------------------------------------

=head3  preprocess_request

  Preprocess the command

=cut

#-------------------------------------------------------
sub preprocess_request
{

    my $request  = shift;
    my $callback = shift;
    my $sub_req  = shift;
    my $command = $request->{command}->[0];
    my $args    = $request->{arg};

    #if already preprocessed, go straight to request
    if ((defined($request->{_xcatpreprocessed}))
        && ($request->{_xcatpreprocessed}->[0] == 1))
    {
        return [$request];
    }


    if ($command eq "ipforward") {
        my $nodes=$request->{node};
	my @sns=();
	if ($nodes) {
	    @sns=@$nodes;
	}
	#print "sns=@sns\n";
	my @requests=();
	foreach (@sns) {
	    my $reqcopy = {%$request};
	    $reqcopy->{'node'}=[];
	    $reqcopy->{'_xcatdest'}=$_;
	    $reqcopy->{_xcatpreprocessed}->[0] = 1;
	    push @requests, $reqcopy;
	}
	return \@requests;
    } else {
	# parse the options
	@ARGV=();
	if ($args) {
	    @ARGV=@{$args};
	}
	Getopt::Long::Configure("bundling");
	Getopt::Long::Configure("no_pass_through");
	
	my $routelist_in;
	my $delete=0;
	if(!GetOptions(
		'h|help'     => \$::HELP,
		'v|version'  => \$::VERSION,
		'r|routename=s'  => \$routelist_in,
		'd|delete'  => \$delete,
	   ))
	{
	    &usage($callback);
	    return 1;
	}
	
	# display the usage if -h or --help is specified
	if ($::HELP) {
	    &usage($callback);
	    return 0;
	}
	
	
	# display the version statement if -v or --verison is specified
	if ($::VERSION)
	{
	    my $rsp={};
	    $rsp->{data}->[0]= xCAT::Utils->Version();
	    $callback->($rsp);
	    return 0;
	}

        #make sure the input routes are in the routes table.
	if ($routelist_in) {
	    my %all_routes=();
	    my $routestab=xCAT::Table->new("routes", -create =>1);
	    if ($routestab) {
		my @tmp1=$routestab->getAllAttribs(('routename', 'net'));
		if (@tmp1 > 0) {
		    foreach(@tmp1) {
			$all_routes{$_->{routename}} = $_;
			$_->{process} = 0;
		    }
		}
	    }
	    
	    my @badroutes=();
	    foreach(split(',', $routelist_in)) {
		if (!exists($all_routes{$_})) {
		    push(@badroutes, $_);
		} 
	    }
	    if (@badroutes>0) {
		my $rsp={};
		my $badroutes_s=join(',', @badroutes);
		if (@badroutes==1) {
		    $rsp->{error}->[0]= "The route $badroutes_s is not defined in the routes table.";
		} else {
		    $rsp->{error}->[0]= "The routes $badroutes_s are not defined in the routes table.";
		}
		$callback->($rsp);
		return 1;
	    }
	} 

	if (@ARGV == 0) { #no noderange is specifiled, assume it is on the mn
	    my $reqcopy = {%$request};
	    $reqcopy->{_xcatpreprocessed}->[0] = 1;
	    if ($routelist_in) {
		$reqcopy->{routelist}->[0]=$routelist_in;
	    }
	    if ($delete) {
		$reqcopy->{delete}->[0]=1;
	    }
	    return [$reqcopy];
	} else { #noderange is specified, 
	    my $ret=[];
	    my $nr=$ARGV[0];
	    my @noderange = xCAT::NodeRange::noderange($nr, 1);
	    my @missednodes=xCAT::NodeRange::nodesmissed();
	    if (@missednodes > 0) {
		my $rsp={};
		$rsp->{error}->[0]= "Invalide nodes in noderange: " . join(',', @missednodes);
		$callback->($rsp);
		return 1;
	    }
	    my @servicenodes=xCAT::Utils->getSNList();

            #print "noderange=@noderange, missednodes=@missednodes, servicenodes=@servicenodes\n"; 

	    #pick out the service nodes from the node list
	    foreach my $sn (@servicenodes) {
		if (grep /^$sn$/, @noderange) {
		    @noderange=grep(!/^$sn$/, @noderange);
		    my $reqcopy = {%$request};
		    $reqcopy->{_xcatpreprocessed}->[0] = 1;
		    $reqcopy->{'_xcatdest'}	= $sn;	
		    $reqcopy->{node} = [$sn];
		    if ($routelist_in) {
			$reqcopy->{routelist}->[0]=$routelist_in;
		    }
		    if ($delete) {
			$reqcopy->{delete}->[0]=1;
		    }
		    push(@$ret, $reqcopy);
		}
	    }
      
            #now find out the service nodes for each node and 
            #send the request to the service node
	    my $sn_hash = xCAT::Utils->get_ServiceNode(\@noderange, "xcat", "MN");
    
	    # build each request for each service node
	    foreach my $sn (keys %$sn_hash)
	    {
		my $reqcopy = {%$request};
		$reqcopy->{node} = $sn_hash->{$sn};
		$reqcopy->{'_xcatdest'} = $sn;
		$reqcopy->{_xcatpreprocessed}->[0] = 1;
		$reqcopy->{remote}->[0] = 1;
		if ($routelist_in) {
		    $reqcopy->{routelist}->[0]=$routelist_in;
		}
		if ($delete) {
		    $reqcopy->{delete}->[0]=1;
		}
		push(@$ret, $reqcopy);
	    }

	    return $ret;
	}
    }
}

#-------------------------------------------------------

=head3  process_request

  Process the command

=cut

#-------------------------------------------------------
sub process_request
{

    my $request  = shift;
    my $callback = shift;
    my $sub_req  = shift;
    my $command = $request->{command}->[0];

    if ($command eq "makeroutes") {
	return process_makeroutes($request, $callback, $sub_req);
    } elsif ($command eq "ipforward") {
	return process_ipforward($request, $callback, $sub_req);
    }
    return;
}

sub process_makeroutes {
    my $request  = shift;
    my $callback = shift;
    my $sub_req  = shift;
    my $command = $request->{command}->[0];

    my $nodes;
    if (exists($request->{node})) {
	$nodes=$request->{node};
    }

    my $delete=0;
    if (exists($request->{delete})) {
	$delete=$request->{delete}->[0];
    }

    my $remote=0;
    if (exists($request->{remote})) {
	$remote=$request->{remote}->[0];
    }


    my $routelist;
    if (exists($request->{routelist})) {
	$routelist=$request->{routelist}->[0];
    }


    #get all the routes from the routes table
    my %all_routes=();
    my $routestab=xCAT::Table->new("routes", -create =>1);
    if ($routestab) {
	my @tmp1=$routestab->getAllAttribs(('routename', 'net', 'mask', 'gateway', 'ifname'));
	if (@tmp1 > 0) {
	    foreach(@tmp1) {
		$all_routes{$_->{routename}} = $_;
		$_->{process} = 0;
	    }
	}
    }
    
    if ($routelist) {
	foreach(split(',', $routelist)) {
	    $all_routes{$_}->{process}=1;
	    if (($nodes) && ($remote)) {
		$all_routes{$_}->{nodes}=$nodes;
	    }
	}
    } else {
	#get the routes for each nodes from the noderes table (for sn and cn) and site table (for mn)
	if ($nodes) {
	    my $nrtab=xCAT::Table->new("noderes", -create =>1);
	    my $nrhash = $nrtab->getNodesAttribs($nodes, ['routenames']) ;
	    foreach(@$nodes) {
		my @badroutes=();
		my $node=$_;
		my $rn;
		my $ent=$nrhash->{$node}->[0];
		if (ref($ent) and defined $ent->{routenames}) {
		    $rn = $ent->{routenames};
		}
		if ($rn) {
		    my @a=split(',', $rn);
		    my @badroutes=();
		    foreach my $r (@a) {
			if (! exists($all_routes{$r})) {
			    push(@badroutes, $r);
			} else {
			    #print "got here...., remote=$remote\n";
			    $all_routes{$r}->{process}=1;
			    if ($remote) {
				my $pa=$all_routes{$r}->{nodes};
				if ($pa) {
				    push(@$pa, $node);
				} else {
				    $all_routes{$r}->{nodes}=[$node];
				}
			    }
			}
		    }
		    if (@badroutes > 0) {
			my $badroutes_s=join(',', @badroutes);
			my $rsp={};
			if (@badroutes==1) {
			    $rsp->{error}->[0]= "The route $badroutes_s is not defined in the routes table. Please check noderes.routenames for node $node.";
			} else {
			    $rsp->{error}->[0]= "The routes $badroutes_s are not defined in the routes table. Please check noderes.routenames for node $node.";
			}
			$callback->($rsp);
			return 1;
		    }
		} else {
		    my $rsp={};
		    $rsp->{data}->[0]= "No routes defined in noderes.routenames for node $node, skiping $node.";
		    $callback->($rsp);
		}
	    }
	}
	else { #this is mn, get the routes from the site table
	    my @mnroutes = xCAT::Utils->get_site_attribute("mnroutenames");
	    if ($mnroutes[0]) {
		my @a=split(',', $mnroutes[0]);
		my @badroutes=();
		foreach my $r (@a) {
		    if (! exists($all_routes{$r})) {
			push(@badroutes, $r);
		    } else {
			$all_routes{$r}->{process}=1;
		    }
		}
		if (@badroutes > 0) {
		    my $badroutes_s=join(',', @badroutes);
		    my $rsp={};
		    if (@badroutes==1) {
			$rsp->{error}->[0]= "The route $badroutes_s is not defined in the routes table. Please check site.mnroutenames for the management node.";
		    } else {
			$rsp->{error}->[0]= "The routes $badroutes_s are not defined in the routes table. Please check site.mnroutenames for the management node.";
		    }
		    $callback->($rsp);
		    return 1;
		}
	    } else {
		my $rsp={};
		$rsp->{data}->[0]= "No routes defined in the site.mnroutenames for the management node.";
		$callback->($rsp);
		return 1;
	    }
	}
    }

    #print Dumper(%all_routes); 

    #now let's handle the route creatation and deletion
    my @sns=(); 
    my $installdir = xCAT::Utils->getInstallDir();
    while (my ($routename, $route_hash) = each(%all_routes)) {
	if ($route_hash->{process}) {
	    my ($gw_name, $gw_ip)=xCAT::NetworkUtils->gethostnameandip($route_hash->{gateway});
	    push(@sns, $gw_name);

	    if ($remote) { #to the nodes
		my $nodes_tmp=$route_hash->{nodes};
		print "nodes=@$nodes_tmp, remote=$remote, delete=$delete\n";
		my $op="add";
		if ($delete)  { $op="delete"; }
		my $output = xCAT::Utils->runxcmd(
		    {
			command => ["xdsh"], 
			node => $nodes_tmp, 
			arg => ["-e", "/$installdir/postscripts/routeop $op " . $route_hash->{net} . " " . $route_hash->{mask} . " $gw_ip"],
			_xcatpreprocessed => [1],
		    }, 
		    $sub_req, -1, 1);
		my $rsp={};
		$rsp->{data}=$output;
		$callback->($rsp);
	    } else { #local on mn or sn
		if ($delete)  {
		    delete_route($callback, $route_hash->{net}, $route_hash->{mask}, $gw_ip, $gw_name);
		} 
		else {
		    set_route($callback, $route_hash->{net}, $route_hash->{mask}, $gw_ip, $gw_name);
		}
	    }
	}
    }
    

    #not all gateways are service nodes
    my %sn_hash=();
    my @allSN=xCAT::Utils->getAllSN();
    my %allSN_hash=();
    foreach(@allSN) {$allSN_hash{$_}=1;}
    foreach my $sn (@sns) {
	if (exists($allSN_hash{$sn})) {
	    $sn_hash{$sn}=1;
	}
    }

    #update servicenode.ipforward 
    my $sntab=xCAT::Table->new("servicenode", -create => 1,-autocommit => 1);
    my %valuehash=();
    my $value=1;
    if ($delete) {$value=0;}
    foreach my $sn (keys %sn_hash)  {
	$valuehash{$sn} = { ipforward=>$value };
    }
    $sntab->setNodesAttribs(\%valuehash);

    #go to the service nodes to enable/disable ipforwarding
    my @nodes=keys(%sn_hash);
    $sub_req->({
	command=>['ipforward'],
	node=>\@nodes,
	arg=>[$delete]}, 
	       $callback);


}

sub process_ipforward {
    my $request  = shift;
    my $callback = shift;
    my $sub_req  = shift;
    my $args    = $request->{arg};

    my $delete=0;
    if ($args) {
	$delete = $args->[0];
    }
    
    if ($delete) {
	xCAT::NetworkUtils->setup_ip_forwarding(0);
    } else {
	xCAT::NetworkUtils->setup_ip_forwarding(1);
    }

}


sub usage {
    my $cb=shift;
    my $rsp={};

    $rsp->{data}->[0]= "Usage: makeroutes -h";
    $rsp->{data}->[1]= "       makeroutes -v";
    $rsp->{data}->[2]= "       makeroutes [-r routename[,routename...]]";
    $rsp->{data}->[3]= "       makeroutes [-r routename[,routename...]] -d ";
    $rsp->{data}->[4]= "       makeroutes noderange [-r routename[,routename...]]";
    $rsp->{data}->[5]= "       makeroutes noderange [-r routename[,routename...]] -d";
    $cb->($rsp);
}

#check if the route exits or not from the route table
sub route_exists {
    my $net = shift;
    my $mask = shift;
    my $gw_ip = shift;
    my $gw=shift;

    my $islinux=xCAT::Utils->isLinux();
    my $result;
    $result=`netstat -nr|grep $net`;
    if ($? == 0) {
	if ($result) {
            my @b=split('\n', $result);
	    foreach my $tmp (@b) {
		chomp($tmp);
		my @a=split(' ', $tmp);
		if ($islinux) { #Linux
		    if (@a >= 3) {
			my $net1=$a[0];
			my $mask1=$a[2];
			my $gw1=$a[1];
			if (($net1 eq $net) && ($mask1 eq $mask) && (($gw1 eq $gw) || ($gw1 eq $gw_ip)))  {
			    return 1;
			}
		    }
		} 
		else { #AIX
		    if (@a >= 2) {
			my $tmp1=$a[0];
			my $gw1=$a[1];

                        #now convert $mask to bits
			$net =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/;
			my $netnum = ($1<<24)+($2<<16)+($3<<8)+$4;
                        my $bits=32;
			while (($netnum % 2) == 0) {
			    $bits--;
			    $netnum=$netnum>>1;
			}
			my $tmp2="$net/$bits";
			if (($tmp1 eq $tmp2) && (($gw1 eq $gw) || ($gw1 eq $gw_ip)))  {
			    return 1;
			}
		    }
		}
	    }
	}
    } 
    return 0;
}

# sets the route with given parameters
sub set_route {
    my $callback=shift;
    my $net = shift;
    my $mask = shift;
    my $gw_ip = shift;
    my $gw=shift;

    my $result;
    if (!route_exists($net, $mask, $gw_ip, $gw)) {
	#set temporay route
        my $cmd;
	if (xCAT::Utils->isLinux()) {
	    $cmd="route add -net $net netmask $mask gw $gw_ip";
	} else {
	    $cmd="route add -net $net -netmask $mask $gw_ip";
	}
	#print "cmd=$cmd\n";
	$result=`$cmd 2>&1`;
	if ($? != 0) {
	    my $rsp={};
	    $rsp->{error}->[0]= "$cmd\nerror code=$?, result=$result\n";
	    $callback->($rsp);
	    return 1;
	} else {
	    #TODO: set per permanent route
	}
    }
    return 0;
}

# deletes the route with given parameters
sub delete_route {
    my $callback=shift;
    my $net = shift;
    my $mask = shift;
    my $gw_ip = shift;
    my $gw=shift;

    my $result;
    if (route_exists($net, $mask, $gw_ip, $gw)) {
	#delete  route temporarily
        my $cmd;
	if (xCAT::Utils->isLinux()) {
	    $cmd="route delete -net $net netmask $mask gw $gw_ip";
	} else {
	    $cmd="route delete -net $net -netmask $mask $gw_ip";
	}
	#print "cmd=$cmd\n";
	$result=`$cmd 2>&1`;
	if ($? != 0) {
	    my $rsp={};
	    $rsp->{error}->[0]= "$cmd\nerror code=$?, result=$result\n";
	    $callback->($rsp);
	    return 1;
	} else {
	    #TODO: delete route permanently
	}
    }
    return 0;
}