# 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::TableUtils;
use xCAT::ServiceNodeUtils;
use xCAT::NetworkUtils;
use xCAT::MsgUtils;
use Getopt::Long;
use xCAT::NodeRange;
use Data::Dumper;
use xCAT::NodeRange;
use IO::File;
use File::Copy;
use File::Path;
use Sys::Hostname;


my $xcat_config_start="# xCAT_CONFIG_START";
my $xcat_config_end="# xCAT_CONFIG_END";



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::ServiceNodeUtils->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::ServiceNodeUtils->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 $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::TableUtils->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::TableUtils->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 ($route_hash->{net} =~ /:/) {
                # Remove the subnet postfix like /64
                if ($route_hash->{net} =~ /\//) {
                    $route_hash->{net} =~ s/\/.*$//;
                }
                # Remove the "/" from the ipv6 prefixlength
                if ($route_hash->{mask}) {
                    if ($route_hash->{mask} =~ /\//) {
                        $route_hash->{mask} =~ s/^\///;
                    }
                }
            }

            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" . " $route_hash->{ifname}"],
                                            _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, $route_hash->{ifname});
                } 
                else {
                    set_route($callback, $route_hash->{net}, $route_hash->{mask}, $gw_ip, $gw_name,$route_hash->{ifname});
                }
            }
        }
    }
    

    #not all gateways are service nodes
    my %sn_hash=();
    my @allSN=xCAT::ServiceNodeUtils->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();

    # ipv6 net
    if ($net =~ /:/) {
        if ($islinux) {
            my $result = `ip -6 route show $net/$mask`;
            # ip -6 route show will return nothing if the route does not exist
            if (!$result || ($? != 0))
            {
                return 0;
            } else {
                return 1;
            }
        } else { # AIX
            # TODO
        }
    } else {
         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;
                             }
                         } # end if (@a >= 2)
                     } #end else linux/aix
                 } # end foreach
             } # end if ($result)
        } # end if ($? == 0 
    } # end else ipv4/ipv6
    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 $ifname=shift;

    my $host=hostname();

    #print "set_route get called\n";

    my $result;
    if (!route_exists($net, $mask, $gw_ip, $gw)) {
        #set temporay route
        my $cmd;
        # ipv6 network
        if ($net =~ /:/) {
            if (xCAT::Utils->isLinux()) {
                $cmd="ip -6 route add $net/$mask via $gw_ip";
            } else {
                # AIX TODO
            }
        } else {
            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";
        my $rsp={};
        $rsp->{data}->[0]= "$host: Adding temporary route: $cmd";
        $callback->($rsp);

        $result=`$cmd 2>&1`;
        if ($? != 0) {
            my $rsp={};
            $rsp->{error}->[0]= "$host: $cmd\nerror code=$?, result=$result\n";
            $callback->($rsp);
            #return 1;
        } 
    } else {
        my $rsp={};
        $rsp->{data}->[0]= "$host: The temporary route already exists for $net.";
        $callback->($rsp);
    }

    #handle persistent routes
    if (xCAT::Utils->isLinux()) { #Linux
        my $os = xCAT::Utils->osver();
        #print "os=$os  $net, $mask, $gw_ip, $gw, $ifname\n";
        if ($os =~ /sles/) { #sles
            addPersistentRoute_Sles($callback, $net, $mask, $gw_ip, $gw, $ifname);
        } elsif ($os =~ /ubuntu|debian/) { #ubuntu or Debian?
            addPersistentRoute_Debian($callback, $net, $mask, $gw_ip, $gw, $ifname);
        }
        elsif ($os =~ /rh|fedora|centos/) { #RH, Ferdora, CentOS
            addPersistentRoute_RH($callback, $net, $mask, $gw_ip, $gw, $ifname);
        }   
    } else { #AIX
        # chdev -l inet0 -a route=net,-hopcount,0,,0,192.168.1.1
        # chdev -l inet0 -a route=net, -hopcount,255.255.255.128,,,,,192.168.3.155,192.168.2.1
        # lsattr -El inet0 -a route
        my $rsp={};
        $rsp->{data}->[0]= "$host: Adding persistent route on AIX is not supported yet.";
        $callback->($rsp);
    }
    
    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 $ifname=shift;

    my $host=hostname();

    #print "delete_route get called\n";

    my $result;
    if (route_exists($net, $mask, $gw_ip, $gw)) {
        #delete  route temporarily
        my $cmd;
        if ($net =~ /:/) {
            if (xCAT::Utils->isLinux()) {
                $cmd = "ip -6 route delete $net/$mask via $gw_ip";
            } else {
                # AIX TODO
            }
        } else {
            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";
        my $rsp={};
        $rsp->{data}->[0]= "$host: Removing the temporary route: $cmd";
        $callback->($rsp);

        $result=`$cmd 2>&1`;
        if ($? != 0) {
            my $rsp={};
            $rsp->{error}->[0]= "$host: $cmd\nerror code=$?, result=$result\n";
            $callback->($rsp);
        }
    }
    else {
        my $rsp={};
        if ($net =~ /:/) {
            $rsp->{data}->[0]= "$host: The temporary route does not exist for $net/$mask.";
        } else {
            $rsp->{data}->[0]= "$host: The temporary route does not exist for $net.";
        }
        $callback->($rsp);
    }

    #handle persistent route
    if (xCAT::Utils->isLinux()) { #Linux
        my $os = xCAT::Utils->osver();
        if ($os =~ /sles/) { #sles
            deletePersistentRoute_Sles($callback, $net, $mask, $gw_ip, $gw, $ifname);
        } elsif ($os =~ /ubuntu/) { #ubuntu or Debian?
            deletePersistentRoute_Debian($callback, $net, $mask, $gw_ip, $gw, $ifname);
        }
        elsif ($os =~ /rh|fedora|centos/) { #RH, Ferdora 
            deletePersistentRoute_RH($callback, $net, $mask, $gw_ip, $gw, $ifname);
        }   
    }
    else { #AIX
        # chdev -l inet0 -a delroute=net,-hopcount,0,,0,192.168.1.1
        # chdev -l inet0 -a delroute=net,-hopcount,255.255.255.128,,,,,192.168.3.128,192.168.2.1
        my $rsp={};
        $rsp->{data}->[0]= "$host: Removing persistent route on AIX is not supported yet.";
        $callback->($rsp);
    }

    return 0;
}


#set the given route to the configuration file  
sub setConfig {
    my $filename=shift;
    my $new_conf_block=shift;
    #print "filename=$filename\n";

    my $new_config=join("\n", @$new_conf_block);
    my $last_char = substr $new_config,-1,1;
    if ($last_char ne "\n") { $new_config .= "\n"; }

    my $filename_tmp = "$filename.$$";
    open (OUTFILE, '>', $filename_tmp);
    my $found=0;
    if (-f $filename) {
	open (INFILE,  '<', $filename);
	my $inblock=0;
	while (<INFILE>) { 
	    my $line=$_;
	    if (!$inblock) {
		print OUTFILE $line;
	    }
	    if ($line =~ /$xcat_config_start/) {
		$found=1;
		$inblock=1;
		print OUTFILE $new_config; 
	    } elsif ($line =~ /$xcat_config_end/) {
		$inblock=0;
		print OUTFILE "$xcat_config_end\n";
	    }
	}
    }
    if (!$found) {
	print OUTFILE "$xcat_config_start\n";
	print OUTFILE $new_config;  
	print OUTFILE "$xcat_config_end\n";
    } 
    close (INFILE);
    close (OUTFILE);
    copy($filename_tmp, $filename);
    unlink($filename_tmp);
}
  
#gets the xCAT configurations from the given file
sub getConfig {
    my $filename=shift;
    my @output=();
    if (-f $filename) { 
	open(FILE, "<", $filename);
	my $xcatconf = 0;
	my $first=0;
	while (<FILE>) {
	    chomp;
	    if (/$xcat_config_start/) {
		$xcatconf = 1;
		$first=1;
	    }
	    elsif (/$xcat_config_end/) {
		$xcatconf = 0;
	    }
	    if ($first) {
		$first=0;
		next;
	    }
	    
	    if ($xcatconf) {
		push @output, $_;
	    }
	}
    }
    return @output;
}

#add the routes to the /etc/sysconfig/network/routes file
#The format is: destination  gateway  mask  ifname
sub addPersistentRoute_Sles {
    my $callback=shift;
    my $net=shift;
    my $mask=shift;
    my $gw_ip=shift;
    my $gw=shift;
    my $ifname=shift;

    my $host=hostname();
    
    my $filename="/etc/sysconfig/network/routes";
    my @output=getConfig($filename);
    #print "old output=" . join("\n", @output) . "\n";
    my $hasConfiged=0;
    if (@output && (@output > 0)) {
	$hasConfiged=checkConfig_Sles($net, $mask, $gw_ip, $gw, \@output);
    }
    #print "hasConfiged=$hasConfiged\n";
    my $new_config;
    if ($net =~ /:/) {
        $new_config = "$net/$mask $gw_ip - -\n";
    } else {
        $new_config="$net $gw_ip $mask $ifname\n";
    }
    if (!$hasConfiged) {
	push(@output, $new_config);
	#print "new output=" . join("\n", @output) . "\n";
	#Add the route to the configuration file
        #the format is: destination  gateway  mask  ifname
	setConfig($filename, \@output);

	chomp($new_config);
	my $rsp={};
	$rsp->{data}->[0]= "$host: Added persistent route \"$new_config\" to $filename.";
	$callback->($rsp);
    } else {
	chomp($new_config);
	my $rsp={};
	$rsp->{data}->[0]= "$host: Persistent route \"$new_config\" already exists in $filename.";
	$callback->($rsp);
    }
}

#remove the routes from the /etc/sysconfig/network/routes file
sub deletePersistentRoute_Sles {
    my $callback=shift;
    my $net=shift;
    my $mask=shift;
    my $gw_ip=shift;
    my $gw=shift;
    my $ifname=shift;
    
    my $host=hostname();

    my $filename="/etc/sysconfig/network/routes";
    my @output=getConfig($filename);
    #print "old output=" . join("\n", @output) . "\n";
    my @new_output=();
    my $bigfound=0;
    foreach my $tmp_conf (@output) {
	my $found = checkConfig_Sles($net, $mask, $gw_ip, $gw, [$tmp_conf]); 
	if (!$found) {
	    push(@new_output, $tmp_conf);
	} else {
	    $bigfound=1;
	}
    }
    #print "new output=" . join("\n", @new_output) . "\n";
    #set the new configuration to the configuration file
    setConfig($filename, \@new_output);
    if ($bigfound) {
	my $rsp={};
        if ($net =~ /:/) {
            $rsp->{data}->[0]= "$host: Removed persistent route \"$net/$mask $gw_ip\" from $filename.";
        } else {
    	    $rsp->{data}->[0]= "$host: Removed persistent route \"$net $gw_ip $mask $ifname\" from $filename.";
        }
	$callback->($rsp);
    } else {
	my $rsp={};
        if ($net =~ /:/) {
	    $rsp->{data}->[0]= "$host: Persistent route \"$net/$mask $gw_ip\" does not exist in $filename.";
        } else {
	    $rsp->{data}->[0]= "$host: Persistent route \"$net $gw_ip $mask $ifname\" does not exist in $filename.";
        }
	$callback->($rsp);
    }
}



#check if the route is in the SLES network configuration file
sub checkConfig_Sles {
    my $net = shift;
    my $mask = shift;
    my $gw_ip = shift;
    my $gw=shift;
    my $output=shift;

    # ipv4 format: 192.168.0.0 207.68.156.51 255.255.0.0 eth1
    # ipv6 format: fd59::/64 fd57:faaf:e1ab:336:21a:64ff:fe01:1 - -
    foreach my $line (@$output) {
	my @a=split(' ', $line);
	my ($net1,$mask1,$gw1);
        if ($net =~ /:/) {
            if (@a>0) {
                my $ipv6net = $a[0];
                ($net1,$mask1) = split("/",$ipv6net);
            }
	    if (@a>1) { 
	        $gw1=$a[1];
	        if ($gw1 eq '-') { $gw1=$gw_ip; }
            }

	} else {
	    if (@a>0) { 
	       $net1=$a[0];
	       if ($net1 eq '-') { $net1=$net;}
	   }
	    if (@a>1) { 
	        $gw1=$a[1];
	        if ($gw1 eq '-') { $gw1=$gw_ip; }
	    }
	    if (@a>2) { 
	        $mask1=$a[2];
	        if ($mask1 eq '-') { $mask1=$mask;}
	    }
       }

	#print "net=$net1,$net mask=$mask1,$mask gw=$gw1,$gw_ip\n";
	if (($net1 && $net1 eq $net) && ($mask1 && $mask1 eq $mask) && (($gw1 && $gw1 eq $gw) || ($gw1 && $gw1 eq $gw_ip)))  {
	    return 1;
	}    
    }
    return 0;
}

#add the routes to the /etc/sysconfig/static-routes file
#The format is: any net 172.16.0.0 netmask 255.240.0.0 gw 192.168.0.1 eth0
sub addPersistentRoute_RH {
    my $callback=shift;
    my $net=shift;
    my $mask=shift;
    my $gw_ip=shift;
    my $gw=shift;
    my $ifname=shift;

    my $host=hostname();
    
    my $filename;
    # ipv6
    if ($net =~ /:/) {
        $filename="/etc/sysconfig/static-routes-ipv6";
    } else {
        $filename="/etc/sysconfig/static-routes";
    }
    my @output=getConfig($filename);
    #print "old output=" . join("\n", @output) . "\n";
    my $hasConfiged=0;
    if (@output && (@output > 0)) {
	$hasConfiged=checkConfig_RH($net, $mask, $gw_ip, $gw, \@output);
    }
    #print "hasConfiged=$hasConfiged\n";
    my $new_config;
    if ($net =~ /:/) {
        # ifname is required for ipv6 routing
        if (!$ifname) {
	        my $rsp={};
	        $rsp->{data}->[0]= "$host: Could not add persistent route for ipv6 network $net/$mask, the ifname is required in the routes table.";
	        $callback->($rsp);
            return;
        }

        $new_config="$ifname $net/$mask $gw_ip";
    } else {
        $new_config="any net $net netmask $mask gw $gw_ip $ifname\n";
    }
    if (!$hasConfiged) {
	push(@output, $new_config);
	#print "new output=" . join("\n", @output) . "\n";
	#Add the route to the configuration file
        #the format is: destination  gateway  mask  ifname
	setConfig($filename, \@output);
	
	chomp($new_config);
	my $rsp={};
	$rsp->{data}->[0]= "$host: Added persistent route \"$new_config\" to $filename.";
	$callback->($rsp);
    } else {
	chomp($new_config);
	my $rsp={};
	$rsp->{data}->[0]= "$host: Persistent route \"$new_config\" already exists in $filename.";
	$callback->($rsp);
    }
}

#remove the routes from the /etc/sysconfig/static-routes file
sub deletePersistentRoute_RH {
    my $callback=shift;
    my $net=shift;
    my $mask=shift;
    my $gw_ip=shift;
    my $gw=shift;
    my $ifname=shift;

    my $host=hostname();
    
    my $filename;
    # ipv6
    if ($net =~ /:/) {
        $filename="/etc/sysconfig/static-routes-ipv6";
    } else {
        $filename="/etc/sysconfig/static-routes";
    }
    my @output=getConfig($filename);
    #print "old output=" . join("\n", @output) . "\n";
    my @new_output=();
    my $bigfound=0;
    foreach my $tmp_conf (@output) {
	my $found = checkConfig_RH($net, $mask, $gw_ip, $gw, [$tmp_conf]); 
	if (!$found) {
	    push(@new_output, $tmp_conf);
	} else {
	    $bigfound=1;
	}
    }
    #print "new output=" . join("\n", @new_output) . "\n";
    #set the new configuration to the configuration file
    setConfig($filename, \@new_output);
    if ($bigfound) {
	my $rsp={};
    if ($net =~ /:/) {
        $rsp->{data}->[0]= "$host: Removed persistent route \"$ifname $net/$mask $gw_ip\" from $filename.";
    } else {
        $rsp->{data}->[0]= "$host: Removed persistent route \"any net $net netmask $mask gw $gw_ip $ifname\" from $filename.";
    }
	$callback->($rsp);
    } else {
	my $rsp={};
    if ($net =~ /:/) {
	    $rsp->{data}->[0]= "$host: Persistent route \"$ifname $net/$mask $gw_ip\" does not exist in $filename.";
    } else {
	    $rsp->{data}->[0]= "$host: Persistent route \"any net $net netmask $mask gw $gw_ip $ifname\" does not exist in $filename.";
    }
	$callback->($rsp);
    }
}

sub checkConfig_RH {
    my $net = shift;
    my $mask = shift;
    my $gw_ip = shift;
    my $gw=shift;
    my $output=shift;

    foreach my $line (@$output) {
	my @a=split(' ', $line);
    #The format is: any net 172.16.0.0 netmask 255.240.0.0 gw 192.168.0.1 eth0
    # ipv6 format: eth1 fd60::/64 fd57::214:5eff:fe15:1
	my ($net1,$mask1,$gw1);
    if ($net =~ /:/) {
        if (@a>1) {
            my $ipv6net = $a[1];
            ($net1,$mask1) = split("/",$ipv6net);
        }
        if (@a>2) {
            $gw1 = $a[2];
        }
    } else {
	    if (@a>2) { 
	        $net1=$a[2];
	        if ($net1 eq '-') { $net1=$net;}
	    }
	    if (@a>4) { 
	        $mask1=$a[4];
	        if ($mask1 eq '-') { $mask1=$mask;}
    	}
    	if (@a>6) { 
	        $gw1=$a[6];
	        if ($gw1 eq '-') { $gw1=$gw_ip; }
	    }
    }

	#print "net=$net1,$net mask=$mask1,$mask gw=$gw1,$gw_ip\n";
	if (($net1 && $net1 eq $net) && ($mask1 && $mask1 eq $mask) && (($gw1 && $gw1 eq $gw) || ($gw1 && $gw1 eq $gw_ip)))  {
	    return 1;
	}    
    }
    return 0;
}


sub addPersistentRoute_Debian{
    my $callback = shift;
    my $net = shift;
    my $mask = shift;
    my $gw_ip = shift;
    my $gw_name = shift;
    my $ifname = shift;
    my $host=hostname();
    my $conf_file = "/etc/network/interfaces.d/$ifname";
    my $cmd = '';
    my $route_conf = '';

    preParse_Debian();

    #ipv6
    if ( $net =~ /:/){
        $cmd = "grep \"$net/$mask gw $gw_ip\" $conf_file";
        $route_conf = "  up route -A inet6 add $net/$mask gw $gw_ip \n  down route -A inet6 del $net/$mask gw $gw_ip \n";
    }
    else { #ipv4
        $cmd = "grep \"-net $net netmask $mask gw $gw_ip\" $conf_file";
        $route_conf = "  up route add -net $net netmask $mask gw $gw_ip \n  down route del -net $net netmask $mask gw $gw_ip \n";
    }

    #fine the corresponding config in the config file
    my @returninfo = `$cmd`;
    if ( @returninfo ){
        my $rsp={};
        $rsp->{data}->[0]= "$host: Persistent route \"$returninfo[0]\" already exists in $conf_file.";
        callback->($rsp);
        return;
    }

    #add the configuration to the config file
    my $readyflag = 0;
    open(FH, "<", $conf_file);
    my @content = <FH>;
    close(FH);

    #read each line of the file and find the insert place
    open(FH, ">", $conf_file);
    foreach my $line ( @content ){
        #add the route line at the end of this dev part
        if (( $readyflag == 1 ) && ( $line =~ /iface|modprobe/ )){
            $readyflag = 0;
            print FH $route_conf;
        }

        if ( $line =~ /iface $ifname/ ){
            $readyflag = 1;
        }
        
        print FH $line;
    }
    
    #the dev is the last one, add the route at the end of the file
    if ( $readyflag == 1 ){
        print FH $route_conf;
    }
    
    close(FH);
}

sub deletePersistentRoute_Debian{
    my $callback=shift;
    my $net=shift;
    my $mask=shift;
    my $gw_ip=shift;
    my $gw=shift;
    my $ifname=shift;

    my $host=hostname();
    my $conf_file = "/etc/network/interfaces.d/$ifname";
    my $match = "";
    my $modflag = 0;

    preParse_Debian();
    #ipv6
    if ( $net =~ /:/){
        $match = "$net/$mask gw $gw_ip";
    }
    else {
        $match = "net $net netmask $mask gw $gw_ip";
    }

    open(FH, "<", $conf_file);
    my @lines = <FH>;
    close(FH);

    open(FH, ">", $conf_file);
    foreach my $line ( @lines ){
        #match the route config, jump to next line
        if ( $line =~ /$match/ ){
            $modflag = 1;
        }
        else{
            print FH $line;
        }
    }
    close(FH);

    my $rsp = {};
    if ( $modflag ){
        $rsp->{data}->[0]= "$host: Removed persistent route \"$match\" from $conf_file.";
    }
    else{
        $rsp->{data}->[0]= "$host: Persistent route \"$match\" does not exist in $conf_file.";
    }

    $callback->($rsp);
    
}

sub preParse_Debian{
    my $configfile;
    
    open(FH, "<", "/etc/network/interfaces");
    my @lines = <FH>;
    close(FH);

    if ($lines[0] =~ /XCAT_CONFIG/i){
        return;
    }

    unless ( -e "/etc/network/interfaces.bak" ){
        copy ("/etc/network/interfaces", "/etc/network/interfaces.bak");
    }

    unless ( -d "/etc/network/interfaces.d" ){
        mkpath( "/etc/network/interfaces.d" );
    }
    
    open(FH, ">", "/etc/network/interfaces");
    print FH "#XCAT_CONFIG\n";
    print FH "source /etc/network/interfaces.d/* \n";
    close(FH);

    foreach my $line ( @lines ){
        if ( $line =~ /^\s*$/){
            next;
        }

        if ( $line =~ /^#.*/ ){
            next;
        }
        
        my @attr = split /\s+/, $line;
        if ( $attr[0] =~ /auto|allow-hotplug/){
            my $i = 1;
            while ( $i < @attr ){
                open(SFH, ">", "/etc/network/interfaces.d/$attr[$i]");
                print SFH "$attr[0] $attr[$i]\n";
                close(SFH);
                print FH "source /etc/network/interfaces.d/$attr[$i] \n";
                $i = $i + 1;
            }
        }
        elsif ($attr[0] =~ /mapping|iface/){
            $configfile = "/etc/network/interfaces.d/$attr[1]";
            open(SFH, ">>", $configfile);
            unless ( -e $configfile){
                print SFH "auto $attr[1] \n";
            }
            print SFH $line;
            close(SFH);
        }
        else{
            open(SFH, ">>", $configfile);
            print SFH $line;
            close(SFH);
        }
    }

    return;
}