#!/usr/bin/perl -w
##################################################################
# (C) Copyright 2010-2015 Hewlett-Packard Development Company, L.P.
# @(#) Serviceguard network creation command
# @(#) Product Name    : HP Serviceguard
# @(#) Product Version : A.12.10.00
# @(#) Patch Name      : 
##################################################################

use strict;
use Pod::Usage;
use File::Copy;

#Make the script immune to localization
$ENV{LC_ALL} = 'C';
$ENV{LANG} = 'C';

my @file_out = undef;
my $SGCONF = undef;
my $SGBIN = undef;
my $NTWRK_SCRIPT = undef;
my $opfile;
my $sgeasylib = undef;

BEGIN {
    my $sgconffile = "/etc/cmcluster.conf";
    if (-f $sgconffile) {
        @file_out = qx(cat $sgconffile);
    } else {
        print STDERR "ERROR: Unable to run cmapplynet ".
                     "command on unsupported OS\n";    
      exit 1;
    }
    
    my @sglib_out = grep(/^SGLIB/, @file_out);
    my @split_sglib_name = split(/=/,$sglib_out[0]);
    chomp($split_sglib_name[1]);
    $sgeasylib = $split_sglib_name[1]; 
}

use lib "$sgeasylib";
use EDTools;
use Data::Dumper;
use Getopt::Long qw(GetOptions HelpMessage :config bundling no_ignore_case);

my @ipv4_lans = ();
my @ipv6_lans = ();
my @probed_ipv4_lans = ();
my @probed_ipv6_lans = ();
my @node_names = ();
my %compareEntryToNetconf = ();
my $parse_failed = 0;
my $sgeasyTool;
my $debug = 0;
my $hostname;

# get our own hostname

my $sgconffile = "/etc/cmcluster.conf";
if (-f $sgconffile) {
    @file_out = qx(cat $sgconffile);
} else {
    print STDERR "ERROR: Unable to run cmapplynet ".
                 "command on unsupported OS\n";    
    exit 1;
}
    
my @sgsbin_out = grep(/^SGSBIN/, @file_out);
my @split_sgsbin_name = split(/=/,$sgsbin_out[0]);
chomp($split_sgsbin_name[1]);
$SGBIN = $split_sgsbin_name[1]."/";

my @sgconf_out = grep(/^SGCONF/, @file_out);
my @split_sgconf_name = split(/=/,$sgconf_out[0]);
chomp($split_sgconf_name[1]);
$SGCONF = $split_sgconf_name[1]."/";

if ($^O eq 'linux') {
    if ($SGCONF eq "/usr/local/cmcluster/conf/") {
        $NTWRK_SCRIPT = "/etc/sysconfig/network-scripts/";    
    }
    else {
        $NTWRK_SCRIPT = "/etc/sysconfig/network/";
    }
    $hostname = `/bin/hostname`;    
}
else {
    $hostname = `/usr/bin/hostname`;
}
chomp $hostname;

my $preview = 0;
GetOptions('-t' => \$preview) or pod2usage();
if (@ARGV != 1) {
    pod2usage("Missing network template file\n");
}
setup_logging();
$sgeasyTool->screen_and_log("Parsing network template...", "cmapplynet:");
parse_template($ARGV[0], \@ipv4_lans, \@ipv6_lans);
validate_sg_version(\@node_names);
$sgeasyTool->screen_and_log("Validating network template...", "cmapplynet:");
my $ret = validate_template_against_self(\@ipv4_lans, \@ipv6_lans); 

my @args = map {"-n " . $_} @node_names;

if ($^O ne 'linux') {
    $opfile = "/etc/cmcluster/net" . "$$";
} else {
    $opfile = $SGCONF . "net" . "$$";
}
$sgeasyTool->screen_and_log("Gathering network configuration. It may take a while...", "cmapplynet:");
system($SGBIN."cmquerycl -N $opfile   @args >/dev/null");
if (-f $opfile) {
    parse_template($opfile, \@probed_ipv4_lans, \@probed_ipv6_lans);
    unlink($opfile);
} else {
    $sgeasyTool->screen_and_log("ERROR: Probed file $opfile doesn't exist", "cmapplynet:");
    exit(-1);
}
$sgeasyTool->screen_and_log("Validating network template against current network configuration...", "cmapplynet:");
my $ipv4_ret = validate_filled_in_template(\@ipv4_lans, "v4");
my $ipv6_ret = validate_filled_in_template(\@ipv6_lans, "v6");

#print "======Dump ipv4_lans=====\n";
#print Dumper(\@ipv4_lans);
#print "======Dump ipv6_lans=====\n";
#print Dumper(\@ipv6_lans);

if (($ipv4_ret == -1) or ($ipv6_ret == -1) or ($ret != 0)) {
    exit(-1);
}
if ($^O eq 'linux') {
    update_network_script_files();
    foreach my $node (@node_names) {
        my $networkManager = 0;
        $sgeasyTool->screen_and_log("Restarting network service on node $node.", "cmapplynet:");
        # Disable service NetworkManager, if it is running
        if ((-f "/etc/redhat-release") && (-f "/var/run/NetworkManager/NetworkManager.pid")) {
            $networkManager = 1; 
            cmexec($node, "/sbin/service NetworkManager stop");
            if ($? != 0) {
                $sgeasyTool->screen_and_log("ERROR: Failed to stop NetworkManager service on node $node.", "cmapplynet:");
                exit(1);
            }
        }
        my $output = cmexec($node, "/sbin/service network restart");
        if ($? != 0) {
            $sgeasyTool->screen_and_log("ERROR: Failed to restart the network service on node $node.", "cmapplynet:");
            exit(1);
        }
        chomp($output);
        if ((-f "/etc/redhat-release") && $networkManager) {
            cmexec($node, "/sbin/service NetworkManager start");
            if ($? != 0) {
                $sgeasyTool->screen_and_log("ERROR: Failed to start NetworkManager service on node $node.", "cmapplynet:");
                exit(1);
            }
        }
        if (index($output,"FAILED")!= -1) {
            $sgeasyTool->screen_and_log("Existing network scripts are backed up at $node:/etc/sysconfig/network-scripts/", "cmapplynet:");
            $sgeasyTool->screen_and_log("Unable to re-start network on $node.", "cmapplynet:");
            $sgeasyTool->screen_and_log("[FAILED] !", "cmapplynet:");
            exit(1);
        }
        $sgeasyTool->screen_and_log("Successfully configured network on node $node...[DONE]", "cmapplynet:");
    }
} else {
    update_netconf_files();   
} 
# update /etc/hosts to contain updated cluster nodes information
my $option = $preview ? "-t":" ";
system($SGBIN . "cmpreparecl -H $option " . "@args");
my $exitStatus = $?;
if ($preview) {
    if ($exitStatus == 0) {
        $sgeasyTool->screen_and_log("Preview complete. To apply changes, run " .
                "cmapplyconf -N without -t option.", undef, $preview);
    }
    else {
        $sgeasyTool->screen_and_log("Preview failed. Fix the issues and re-run " .
                "cmapplyconf -N with -t option.", undef, $preview);
    }
}
exit($exitStatus);


sub fail {
    my $string = shift;
    $sgeasyTool->screen_and_log("$string", "cmapplynet:");
    cleanup_temp_files();
    exit(1);
}

sub rtrim($)
{
    my $string = shift;
    $string =~ s/\s+$//;
    return $string;
}

# Find the entry with the given (key, value) in the given array.
sub found_entry {
    my ($array_of_entries, $key, $value) = @_;
    
    foreach my $entry (@$array_of_entries) {
       if ($entry->{ lc($key) } eq $value) {
           return $entry;
       }
    }
    return 0;
}

# Validate the data from the template.
# If the given key is not configured yet, the data is considered valid.
# If the given key is already configured with the given data, the data is 
# also considered valid.
# If the given key has been configured with a different value, the data 
# is considered invalid, and we should fail the request.
#
sub valid_configuration {
    my ($entry, $key, $value) = @_;

    my $current_value = $entry->{ lc($key) };
    if (($current_value) and ($current_value ne $value)) {
         # Fail if the key has been configured with a different value
         $sgeasyTool->screen_and_log("ERROR: line# $. :  $key is configured as $current_value. Reconfiguring to $value is not allowed.", "cmapplynet:");
         $parse_failed = 1;
         return 0;
    }

    return 1;

}

sub parse_template {

    my ($template_file, $v4_lans, $v6_lans) = @_;
    my $node_list = 0;;
    my $intf_list = 0;;
    my $connectedlan = 0;;
    my $node_info = 0;;
    my $intf_info = 0;; 
    my $ipv6_key = 0;;
    my $first_ipv4_bridged_net = 1;
    my $first_ipv6_bridged_net = 1;
    my $lanref;

    open(SRC, "<$template_file") or fail "Failed to open $template_file";
    while (<SRC>) {
       chomp;

        next if (/^\s*#/) or (/^\s*$/);

        if (/^\s*IPV4_BRIDGED_NET:(\s*\d+)/)  {

            if ($first_ipv4_bridged_net) {
               $lanref = $v4_lans;
               $first_ipv4_bridged_net = 0;
            }

            $intf_list = 0;
            $node_info = 0;
            $intf_info = 0;

            $connectedlan = {};
            $node_list = ();
            $connectedlan->{ "connected_lan" } = $1;
            $connectedlan->{ "node_list" } = \@$node_list;
            push @$lanref, $connectedlan;

        }
        elsif (/^\s*IPV6_BRIDGED_NET:(\s*\d+)/) {

            if ($first_ipv6_bridged_net) {
               $lanref = $v6_lans;
               $first_ipv6_bridged_net = 0;
            }

            $intf_list = 0;
            $node_info = 0;
            $intf_info = 0;

            $connectedlan = {};
            $node_list = ();
            $connectedlan->{ "connected_lan" } = $1;
            $connectedlan->{ "node_list" } = \@$node_list;
            push @$lanref, $connectedlan;

        }
        elsif (/^\s*NODE/) {
            /(\S+)\s*\:\s*(\S*)/;

            if (!$node_list) {
                $sgeasyTool->screen_and_log("ERROR: line# $. :  Must configure  IPV4_BRIDGED_NET or " .
                                            "IPV6_BRIDGED_NET before configuring $1", "cmapplynet:");
                $parse_failed = 1;
            }
            elsif ($node_info = found_entry($node_list, "node_name", $2)) {
                # If the node is configured already, do not add a duplicate entry.
                $intf_list = $node_info->{ "intf_list" };
            } else {

                $node_info = {};
                $node_info->{ "node_name" } =  $2;
                $intf_list = ();
                $intf_info = 0; 
                $node_info->{ "intf_list" } = \@$intf_list;
                push @$node_list, $node_info;
                if (!(grep /$2/, @node_names)) {
                    push(@node_names, $2);
                }
            }
        }
        elsif (/^\s*INTERFACE_NAME/) {
            /(\S+)\s*\:\s*(\S*)/;

            if (!$intf_list) {
                $sgeasyTool->screen_and_log("ERROR: line# $. : Must configure NODE before configuring $1",
                                            "cmapplynet:");
                $parse_failed = 1;
            }
            elsif (!($intf_info = found_entry($intf_list, "intf_name", $2))) {
                $intf_info = {};
                $intf_info->{ "intf_name" } = $2;
                push @$intf_list, $intf_info;
            }
        } 
        elsif (/^\s*IPV6_SECONDARY_INTERFACE_NAME/) {
            /(\S+)\s*\:\s*(\S*)/;

            if (!$intf_list) {
                $sgeasyTool->screen_and_log("ERROR: line# $. : Must configure NODE before configuring $1",
                                            "cmapplynet:");
                $parse_failed = 1;
            }
            elsif (!($intf_info = found_entry($intf_list, "intf_name", $2))) {
                $intf_info = {};
                $intf_info->{ "intf_name" } = $2;
                push @$intf_list, $intf_info;
            }
        } 
        elsif (/^\s*IP_ADDRESS/) {
            /(\S+)\s*\:\s*(\S*)/;

            next if (!$2);

            if (!$intf_info) {
                $sgeasyTool->screen_and_log("ERROR: line# $. : Must configure INTERFACE before configuring $1",
                                            "cmapplynet:");
                $parse_failed = 1;
            }
            elsif (valid_configuration($intf_info, $1, $2)) {
                $intf_info->{ lc($1) } = $2;
            }
        }
        elsif (/^\s*IPV6_ADDRESS/) {
            /(\S+)\s*\:\s*(\S*)/;

            next if (!$2);

            my $ip6_addr = $2;
            if (!$intf_info) {
                $sgeasyTool->screen_and_log("ERROR: line# $. : Must configure INTERFACE before configuring $1",
                                            "cmapplynet:");
                $parse_failed = 1;
            }
            else {
                if ($ip6_addr =~ /^fe[89ab]/) {
                    $sgeasyTool->screen_and_log("ERROR: line# $. : Configuring link-local addresses is not supported.", "cmapplynet:");
                    $parse_failed = 1;
                } elsif ($ip6_addr =~ /^fe[cdef]/) {
                    $ipv6_key = "ip6_site_addr";
                } else {
                    $ipv6_key = "ip6_global_addr";
                }

                if (valid_configuration($intf_info, $ipv6_key, $ip6_addr)) {
                    $intf_info->{ $ipv6_key } = $ip6_addr;
                }
            }
        }
        elsif (/^\s*IPV6_PREFIXLEN/ or /^\s*SUBNET_MASK/) {
            /(\S+)\s*\:\s*(\S*)/;

            next if (!$2);

            if (!$intf_info) {
                $sgeasyTool->screen_and_log("ERROR: line# $. : Must configure INTERFACE before configuring $1",
                                            "cmapplynet:");
                $parse_failed = 1;
            }
            elsif (valid_configuration($intf_info, $1, $2)) {
                $intf_info->{ lc($1) } = $2;
            }
        }
        else {
            $sgeasyTool->screen_and_log("ERROR: line# $. : Unrecognized keyword: $_", "cmapplynet:");
            $parse_failed = 1;
       }
    }
    close SRC;

    if ($parse_failed) {
        fail "Invalid data in $template_file\n";
    }
}


sub validate_filled_in_template {
    my ($lans, $ver) = @_;
    my $ret = 0;
    my $j = 0;
    my $global_ret = 0;

    foreach my $br_net (@{$lans}) {
        my %brnet = %{$br_net};
        my $br_net_num = $brnet{connected_lan};
        my @node_list = @{$brnet{node_list}};
        foreach my $node (@node_list) {
            my %node_entry = %{$node};
            my $node_nm = $node_entry{node_name};
            my @intf_list = @{$node_entry{intf_list}};

            foreach my $intf (@intf_list) {
                my %intf_entry = %{$intf};
                my $intf_nm;
                if ($ver eq "v4") {
                    $intf_nm = $intf_entry{intf_name};
                    my $ip_addr;
                    if (!defined($intf_entry{ip_address})) {
                        $ip_addr = undef;
                    } 
                    else {
                        $ip_addr = $intf_entry{ip_address};
                    }
                    my $netmask;
                    if (!defined($intf_entry{subnet_mask})) {
                        $netmask = undef;
                    }
                    else {
                        $netmask = $intf_entry{subnet_mask};
                    }
                    $ret = validate_against_probed_data($node_nm, $br_net_num, 
                                          $netmask, $intf_nm, $ip_addr, 
                                          scalar(@node_list), $ver);
                    if ($ret == -1) {
                        $global_ret = -1;
                    }
                    if (1 == $ret) {
                        $intf->{new_br_net} = "yes";
                    }
                    else {
                        $intf->{new_br_net} = "no";
                    } 
                    next if (!defined($ip_addr));
                    if ($global_ret != -1) {
                        if (defined ($compareEntryToNetconf{$node_nm}->{'ipv4'})) {
                            $j = scalar @{$compareEntryToNetconf{$node_nm}->{'ipv4'}};
                        }
                        else {
                            $j = 0;
                        }
                        @{$compareEntryToNetconf{$node_nm}->{'ipv4'}[$j]} =
                             ($intf_nm, $ip_addr, $netmask, $intf->{new_br_net});
                    }
                }
                elsif ($ver eq "v6") {
                    $intf_nm = $intf_entry{intf_name};
                    my $ip_addr;
                    if (!defined($intf_entry{ip6_global_addr})) {
                        $ip_addr = undef;
                    }
                    else {
                        $ip_addr = $intf_entry{ip6_global_addr};
                    }
                    my $netmask = undef;
                    $ret = validate_against_probed_data($node_nm, $br_net_num, 
                                                 $netmask, $intf_nm, $ip_addr, 
                                                 scalar(@node_list), $ver);
                    if ($ret == -1) {
                        $global_ret = -1;
                    }
  
                    if (1 == $ret) {
                        $intf->{new_br_net} = "yes";
                    }
                    else {
                        $intf->{new_br_net} = "no";
                    }
                    if (defined($ip_addr) and ($global_ret != -1)) {
                        if (defined ($compareEntryToNetconf{$node_nm}->{'ipv6'})) {
                            $j = scalar @{$compareEntryToNetconf{$node_nm}->{'ipv6'}};
                        }
                        else {
                            $j = 0;
                        }
                        @{$compareEntryToNetconf{$node_nm}->{'ipv6'}[$j]} =
                            ($intf_nm, $ip_addr, $intf->{ipv6_prefixlen}, $intf->{new_br_net});
                    }

                    if (!defined($intf_entry{ip6_site_addr})) {
                        $ip_addr = undef;
                    }
                    else {
                        $ip_addr = $intf_entry{ip6_site_addr};
                    }
                    $netmask = undef;
                    $ret = validate_against_probed_data($node_nm, $br_net_num, 
                                     $netmask, $intf_nm, $ip_addr, 
                                     scalar(@node_list), "sv6");

                    if ($ret == -1) {
                        $global_ret = -1;
                    }

                    if (1 == $ret) {
                        $intf->{new_br_net} = "yes";
                    }
                    else {
                        $intf->{new_br_net} = "no";
                    }
                    if (defined($ip_addr) and ($global_ret != -1)) {
                        if (defined ($compareEntryToNetconf{$node_nm}->{'ipv6'})) {
                            $j = scalar @{$compareEntryToNetconf{$node_nm}->{'ipv6'}};
                        }
                        else {
                            $j = 0;
                        }
                        @{$compareEntryToNetconf{$node_nm}->{'ipv6'}[$j]} =
                            ($intf_nm, $ip_addr, $intf->{ipv6_prefixlen}, $intf->{new_br_net});
                    }
                }
            }
        }
    }
    return ($global_ret);
}

sub validate_against_probed_data {
    my ($node_nm, $br_net_num, $netmask,
                $intf_nm, $ip_addr, $num_of_nodes, $ver) = @_;
    my $lan_found = 0;
    my $ip_key;
    my $mask_key;
    my $lans;
    my $node_found = 0;
    my $global_ret = 0;
    my $intf_found = 0;
    my $br_key = "BRIDGED_NET";

    if ($ver eq "v4") {
        $lans = \@probed_ipv4_lans;
        $ip_key = "ip_address";
        $mask_key = "subnet_mask";
        $br_key = "IPV4_BRIDGED_NET";
    }
    elsif ($ver eq "v6") {
        $lans = \@probed_ipv6_lans;
        $ip_key = "ip6_global_addr";
        $mask_key = "global_netmask";
        $br_key = "IPV6_BRIDGED_NET";
    }
    else {
        $lans = \@probed_ipv6_lans;
        $ip_key = "ip6_site_addr";
        $mask_key = "site_netmask";
        $br_key = "IPV6_BRIDGED_NET";
    }
    my %br_net_h;
    foreach my $tmp_br_net (@$lans) {
        %br_net_h = %{$tmp_br_net};
        if ($br_net_num == $br_net_h{connected_lan}) {
            $lan_found = 1;
            last;
        }
    }
    if ($lan_found == 0) {
        $sgeasyTool->screen_and_log("ERROR: Template has $br_key $br_net_num " .
                                    "that is not in the probed data. ", 
                                    "cmapplynet:");
        $global_ret = -1; 
    }

    my @node_list = @{$br_net_h{node_list}};
    my @nodes;
    foreach my $node_entry (@node_list) {
        my %nh = %{$node_entry};
        push @nodes, $nh{node_name};
    }

    if (scalar(@node_list) < $num_of_nodes) {
        $sgeasyTool->screen_and_log("ERROR: More nodes specified for $br_key: " .
                                    "$br_net_num than identified by " .
                                    "Serviceguard: nodes - @nodes. \n" . 
                                    "Number of nodes: " . scalar(@nodes)  . 
                                    "Input file has: $num_of_nodes ", 
                                    "cmapplynet:");
        $global_ret =  -1;
    }
    my %node_entry_h;
    foreach my $node_entry (@node_list) {
        %node_entry_h = %{$node_entry};

        if ($node_nm eq $node_entry_h{node_name}) {
            $node_found = 1;
            last;
        }
    }
    if ($node_found == 0) {
        $sgeasyTool->screen_and_log("ERROR: Input node $node_nm for $br_key " .
                                    "$br_net_num is not in the probed data ",
                                    "cmapplynet:");
        $global_ret = -1; 
    }
    my @intf_entry_list = @{$node_entry_h{intf_list}};
    foreach my $intf_iter (@intf_entry_list) {
        my %intf_iter_h = %{$intf_iter};
        if ($intf_nm eq $intf_iter_h{intf_name}) {
            $intf_found = 1;

            #Verify that subnet_mask is not modified
            if (defined($netmask)) {
                if ($intf_iter_h{$mask_key} && ($intf_iter_h{$mask_key} ne "")) {
                    if ($intf_iter_h{$mask_key} ne $netmask) {
                        $sgeasyTool->screen_and_log("ERROR: Detected change ".
                                     "in IP subnet mask for interface " .
                                     "$intf_nm. Probed subnet mask: " . 
                                     "$intf_iter_h{$mask_key}. Input file " .
                                     "has: $netmask ", "cmapplynet:");
                        $sgeasyTool->screen_and_log("Modification of subnet " .
                                     "mask is not allowed ", "cmapplynet:");
                        $global_ret = -1;
                    }
                }
                else {
                    #If it is in the template and not in probed data
                    #then the user is configuring new interface.
                    $global_ret = 1;
                }
            }
            else {
                #If the filled-in template doesn't have subnet_mask,
                #but it is in probed data that means sbnet_mask is being
                #removed. This is not allowed
                if (exists($intf_iter_h{$mask_key}) and
                              exists($intf_iter_h{$mask_key})) {
                    $sgeasyTool->screen_and_log("ERROR: Detected deletion of " .
                                 "IP subnet mask for interface $intf_nm. " .
                                 "Probed subnet: " . $intf_iter_h{$mask_key}, 
                                                "cmapplynet:");
                    $sgeasyTool->screen_and_log("Deletion of subnet mask is " .
                                                "not allowed ", "cmapplynet:");
                    $global_ret =  -1;
                }
            }

            #Check if IP address is modified
            if (defined($ip_addr)) {
                if (defined($intf_iter_h{$ip_key})) {   
                    if ($intf_iter_h{$ip_key} ne $ip_addr) {
                        $sgeasyTool->screen_and_log("ERROR: Detected change of IP address for " .
                                                    "interface $intf_nm on node $node_nm. \n" .
                                                    "Existing address: $intf_iter_h{$ip_key} " .
                                                    "New address: $ip_addr ", "cmapplynet:");
                        $sgeasyTool->screen_and_log("    Modification of addresses is not allowed ",
                                                    "cmapplynet:");
                        return -1;
                    }
                    else {
                        return $global_ret;
                    }
                }
                else {
                    #If it is in the template and not in probed data
                    #then the user is configuring new interface.
                    if ($global_ret != -1) {
                        return 1;
                    }
                    else {
                        return $global_ret;
                    }
                }
            }
            else {
                if (defined($intf_iter_h{$ip_key})) {
                    $sgeasyTool->screen_and_log("ERROR: Detected deletion of IP address for " .
                                                "$intf_nm on node $node_nm. \n" .
                                                "Existing address: $intf_iter_h{$ip_key} " . 
                                                "$br_key id: $br_net_num ", "cmapplynet:");
                    $sgeasyTool->screen_and_log("    Deletion of addresses is not allowed ", 
                                                "cmapplynet:");
                    return -1;
                }
                else {
                    #Should be unreachable code, just having it for 
                    #completeness
                    return $global_ret;
                }
            }
        }
    }
    if (0 == $intf_found) {
        $sgeasyTool->screen_and_log("ERROR: Didn't find $intf_nm on the bridged net $br_net_num " .
                                    " for node $node_nm ", "cmapplynet:");
        return -1;
    }
}

sub update_netconf_files {
    my @netconf_files = ("netconf", "netconf-ipv6");
    my @tempFiles = ();

    foreach my $node (@node_names) {
       foreach my $netconf (@netconf_files) {
         my $exit_code = cmcp($node, "/etc/rc.config.d/$netconf", $hostname, "/tmp/$netconf.tmp.$node");
         if ($exit_code != 0) {
             cleanup_temp_files(@tempFiles);
             $sgeasyTool->screen_and_log("ERROR: Failed to copy netconf file $netconf.tmp.$node in " .
                                         "/tmp from node $node", "cmapplynet:");
             exit(1);
         }
         push(@tempFiles, "/tmp/$netconf.tmp.$node");
       }
    }
    check_syntax_netconf(@tempFiles);
    update_validate_netconf_temp(@tempFiles);

    # backup original netconf and netconf-ipv6 files
    if (! (-d "/var/adm/cmcluster/backup")) {
        my $create_dir = mkdir "/var/adm/cmcluster/backup", 0744;
        if ($create_dir != 1) {
            cleanup_temp_files(@tempFiles);
            $sgeasyTool->screen_and_log("ERROR: Failed to create directory: /var/adm/cmcluster/backup",
                                        "cmapplynet:");
            exit(1);
        }
    }
    foreach my $node (@node_names) {
       foreach my $netconf (@netconf_files) {
         my $saved_netconf = "/var/adm/cmcluster/backup/$netconf.$node" . "$$";
         my $exit_code = cmcp($node, "/etc/rc.config.d/$netconf", $hostname, $saved_netconf);
         if ($exit_code != 0) {
             cleanup_temp_files(@tempFiles);
             $sgeasyTool->screen_and_log("ERROR: Failed to backup /etc/rc.config.d/$netconf from " .
                                         "node $node to $saved_netconf", "cmapplynet:");
             exit(1);
         }
         $sgeasyTool->screen_and_log("/etc/rc.config.d/$netconf from node " .
                       "$node is saved to $saved_netconf on local node", "cmapplynet:");
       }
    }

    # overwrite original netconf and netconf-ipv6 files with updated files
    my %netconf_changed;
    foreach my $node (@node_names) {
       my @changed = ();
       foreach my $netconf (@netconf_files) {
          my $exit_code = 0;
          my $saved_netconf = "/var/adm/cmcluster/backup/$netconf.$node" . "$$";
          my $working_netconf = "/tmp/$netconf.tmp.$node";
          # Skip the netconf deploy/restart if no change was made
          # The code might need rework if the working netconf was 
          # re-generated instead of appended for newly added interfaces
          `diff $working_netconf $saved_netconf`;
          $exit_code = `echo $?`;
          next if ($exit_code == 0);
          push(@changed, $netconf);
          $exit_code  = cmcp($hostname, "/tmp/$netconf.tmp.$node", 
                            $node, "/etc/rc.config.d/$netconf") unless ($preview);
          if (($exit_code != 0) and (!$preview)) {
              cleanup_temp_files(@tempFiles);
              $sgeasyTool->screen_and_log("ERROR: Failed to update /etc/rc.config.d/$netconf on " .
                                          "node $node. Restore all netconf files", "cmapplynet:");
              exit(1);
          }
       }
       $netconf_changed{$node} = \@changed;
    }
    if ($preview) {
        cleanup_temp_files(@tempFiles);
        return;
    }

    foreach my $node (@node_names) {
       my @changed = @{$netconf_changed{$node}};
       for my $netconf (@changed) {
           if ($netconf eq "netconf") {
               $sgeasyTool->screen_and_log("Running /sbin/init.d/net start on $node", "cmapplynet:");
               cmexec($node, "/sbin/init.d/net start");
               if ($? != 0) {
                   cleanup_temp_files(@tempFiles);
                   $sgeasyTool->screen_and_log("netconf and netconf-ipv6 files are backed up at /var/adm/cmcluster/backup/ ", "cmapplynet:");
                   $sgeasyTool->screen_and_log("Failed to re-start ipv4 network.", "cmapplynet:");
                   exit(1);
               } 
           }
           if ($netconf eq "netconf-ipv6") {
               $sgeasyTool->screen_and_log("Running /sbin/init.d/net-ipv6 start on $node", "cmapplynet:");
               cmexec($node, "/sbin/init.d/net-ipv6 start");
               if ($? != 0) {
                   cleanup_temp_files(@tempFiles);
                   $sgeasyTool->screen_and_log("netconf and netconf-ipv6 files are backed up at /var/adm/cmcluster/backup/ ", "cmapplynet:");
                   $sgeasyTool->screen_and_log("Failed to re-start ipv4 network.", "cmapplynet:");
                   exit(1);
               }
           }
        }
    }

    # cleanup temporary files
    cleanup_temp_files(@tempFiles);
}

sub
update_validate_netconf_temp {
   my (@tempFiles) = @_;
   $sgeasyTool->screen_and_log("ERROR: Missing netconf temporary files", "cmapplynet:") if (! @tempFiles);
   my $netconf_temp_file;
   my $highest_index;
   my $highestPrimaryIPv6Index;
   my %netconfParsedData;
   my $netconfParsedDataRef;
   my @definedPrimaryIPv6;
   my $definedPrimaryIPv6Ref;
   my %addedIPv4EntryComment;
   my %addedIPv6EntryComment;
   my $timestamp = localtime;
   

   foreach my $node (keys %compareEntryToNetconf) {
      foreach my $ip_version (keys %{$compareEntryToNetconf{$node}}) {
         if ($ip_version eq "ipv4") {
             ($netconf_temp_file) = grep{$_ =~ /$node/ and  $_ !~ /netconf-ipv6/} @tempFiles;
         }
         elsif ($ip_version eq "ipv6") {
             ($netconf_temp_file) = grep{$_ =~ /$node/ and  $_ =~ /netconf-ipv6/} @tempFiles;
         }
         if (! $netconf_temp_file) {
             cleanup_temp_files(@tempFiles);
             $sgeasyTool->screen_and_log("ERROR: missing netconf file", "cmapplynet:");
             exit(1);
         }
         my $netconf_file;
         if ($ip_version eq "ipv6") {
             $netconf_file = "/etc/rc.config.d/netconf-ipv6";
         } else {
             $netconf_file = "/etc/rc.config.d/netconf";
         }

         #open netconf file
         open(NETCONF_FILE, ">>$netconf_temp_file") || (cleanup_temp_files(@tempFiles) and 
                                                    fail("Failed to open file $netconf_temp_file"));
         ($highest_index, $highestPrimaryIPv6Index, $netconfParsedDataRef, $definedPrimaryIPv6Ref) = netconf_parser($netconf_temp_file, $ip_version, \@tempFiles);
         @definedPrimaryIPv6 = @$definedPrimaryIPv6Ref;
         %netconfParsedData = %$netconfParsedDataRef;

         for my $i ( 0 .. $#{ $compareEntryToNetconf{$node}->{$ip_version} } ) {
             my ($intf_nm, $ip_addr, $netmaskOrprefixlen, $new) = 
                      @{$compareEntryToNetconf{$node}->{$ip_version}[$i]};
             # make sure entry doesn't exist in netconf before appending
             # validate old entry in template exists in netconf
             my $validated_old_entry = 0;
             for my $entry ( 0 .. $#{ $netconfParsedData{$ip_version} } ) {
                 my ($netconf_intf, $netconf_ip, $netconf_netmOrpref) =
                                    @{$netconfParsedData{$ip_version}[$entry]};
                 # make sure entry doesn't exist in netconf before appending
                 if (($new eq "yes") and (($netconf_intf eq $intf_nm) || 
                                                   ($netconf_ip eq $ip_addr))) {
                     close(NETCONF_FILE);
                     cleanup_temp_files(@tempFiles);
                     $sgeasyTool->screen_and_log("ERROR: $intf_nm or $ip_addr already exists in netconf " . 
                                                 "file on node $node", "cmapplynet:");
                     exit(1);
                 }
                 elsif ($new eq "no") {
                        # validate old entry in template exists in netconf
                        if ($ip_version eq "ipv6") {
                            my $expanded_netconf_ipv6 = expand_ipv6_addr($netconf_ip);
                            my $expanded_template_ipv6 = expand_ipv6_addr($ip_addr);
                            if ($expanded_netconf_ipv6 eq $expanded_template_ipv6) {
                                if (! $netconf_netmOrpref or ($netconf_netmOrpref and 
                                         ($netconf_netmOrpref eq $netmaskOrprefixlen))) {
                                    $validated_old_entry = 1;
                                    last;
                                }
                            }
                        }
                        if ($ip_version eq "ipv4") {
                            if (($netconf_netmOrpref !~ /\./) && ($netconf_netmOrpref =~ /[0-9A-Fa-f]{8}$/)) {
                                if (($netconf_netmOrpref !~ m/^0x/) || (length($netconf_netmOrpref) != 10)) {
                                    $sgeasyTool->screen_and_log("ERROR: subnet mask $netconf_netmOrpref " .
                                                                "for interface $intf_nm with IP $ip_addr " .
                                                                "in netconf on $node  must be specified " .
                                                                "either as a single hexadecimal number " .
                                                                "with a leading 0x or with a dot-notation " .
                                                                "Internet address", "cmapplynet:");
                                    close(NETCONF_FILE);
                                    cleanup_temp_files(@tempFiles);
                                    exit(1);
                                }
                                $netconf_netmOrpref = netmask_hex_to_decimal_dot($netconf_netmOrpref);
                            }
                            if (($netconf_intf eq $intf_nm) and ($netconf_ip eq $ip_addr)
                                          and ($netconf_netmOrpref eq $netmaskOrprefixlen)) {
                                $validated_old_entry = 1;
                                last;
                            }
                        }
                 }
             }
             if (($new eq "no") and (! $validated_old_entry)) {
                 close(NETCONF_FILE);
                 cleanup_temp_files(@tempFiles);
                 $sgeasyTool->screen_and_log("ERROR: netmask or prefixlen " .
                                      "$netmaskOrprefixlen is missing in " .
                                      "netconf on $node for $intf_nm with " .
                                      "IP $ip_addr", "cmapplynet:");
                 exit(1);
             }
             next if ($new eq "no");
             #append new entry to netconf file
             $highest_index = $highest_index + 1;
             if ($ip_version eq "ipv4") {
                 if (!$addedIPv4EntryComment{$node}++) {
                     print NETCONF_FILE "#Added automatically by Serviceguard" .
                                        " on $timestamp\n";
                 }

                 print NETCONF_FILE "INTERFACE_NAME[$highest_index]=$intf_nm\n";
                 print NETCONF_FILE "IP_ADDRESS[$highest_index]=$ip_addr\n";
                 print NETCONF_FILE "SUBNET_MASK[$highest_index]=$netmaskOrprefixlen\n";
             }
             elsif ($ip_version eq "ipv6") {
                 if (!$addedIPv6EntryComment{$node}++) {
                     print NETCONF_FILE "#Added automatically by Serviceguard" .
                                        " on $timestamp\n";
                 }

                 my ($addrPrefix) = split(/:/, $intf_nm);
                 if (0 == (scalar(grep(/$addrPrefix/, @definedPrimaryIPv6)))) {
                     $highestPrimaryIPv6Index++;
                     print NETCONF_FILE "IPV6_INTERFACE[$highestPrimaryIPv6Index]=$addrPrefix\n";
                     print NETCONF_FILE "IPV6_INTERFACE_STATE[$highestPrimaryIPv6Index]=\"up\"\n\n";
                     push(@definedPrimaryIPv6, $addrPrefix);
                 } 
 
                 print NETCONF_FILE "IPV6_SECONDARY_INTERFACE_NAME[$highest_index]=$intf_nm\n";
                 print NETCONF_FILE "IPV6_ADDRESS[$highest_index]=$ip_addr\n";
                 print NETCONF_FILE "IPV6_PREFIXLEN[$highest_index]=$netmaskOrprefixlen\n\n";
             }
         }
         close(NETCONF_FILE);

         # now validate extra [intf, ip, netm-pref] doesn't exist in netconf
         ($highest_index, $highestPrimaryIPv6Index, $netconfParsedDataRef, $definedPrimaryIPv6Ref) = netconf_parser($netconf_temp_file, $ip_version, \@tempFiles);
         %netconfParsedData = %$netconfParsedDataRef;
         if ((scalar @{$netconfParsedData{$ip_version}}) != (scalar @{$compareEntryToNetconf{$node}->{$ip_version}})) {
             cleanup_temp_files(@tempFiles);

             $sgeasyTool->screen_and_log("ERROR: Unexpected entries in $netconf_file file on node $node",
                                         "cmapplynet:");
             exit(1);
         }
         # validate newly added entries to netconf are correct
         for my $j ( 0 .. $#{ $compareEntryToNetconf{$node}->{$ip_version} } ) {
             my ($intf_nm, $ip_addr, $netmaskOrprefixlen, $new) =
                      @{$compareEntryToNetconf{$node}->{$ip_version}[$j]};
             next if ($new eq "no");
             my $validated_new_entry = 0;
             for my $entry ( 0 .. $#{ $netconfParsedData{$ip_version} } ) {
                 my ($netconf_intf, $netconf_ip, $netconf_netmOrpref) =
                                  @{$netconfParsedData{$ip_version}[$entry]};

                 if ($ip_version eq "ipv6") {
                     my $expanded_netconf_ipv6 = expand_ipv6_addr($netconf_ip);
                     my $expanded_template_ipv6 = expand_ipv6_addr($ip_addr);
                     if ($expanded_netconf_ipv6 eq $expanded_template_ipv6) {
                         if (! $netconf_netmOrpref or ($netconf_netmOrpref and ($netconf_netmOrpref eq $netmaskOrprefixlen))) {
                             $validated_new_entry = 1;
                             last;
                         }
                     }
                 }
                 elsif (($netconf_intf eq $intf_nm) and ($netconf_ip eq $ip_addr)
                                    and ($netconf_netmOrpref eq $netmaskOrprefixlen)) {
                      $validated_new_entry = 1;
                      last;
                 }
         }
         if (! $validated_new_entry) {
             close(NETCONF_FILE);
             cleanup_temp_files(@tempFiles);
             $sgeasyTool->screen_and_log("ERROR: New $intf_nm with $ip_addr with $netmaskOrprefixlen " .
                                         "missing from $netconf_file on node $node", "cmapplynet:");
             exit(1);
         }
        }
      }
   }
}


sub
netconf_parser {
   my ($netconf_temp_file, $ip_version, $tempFilesRef) = @_;
   if (! $netconf_temp_file) {
       $sgeasyTool->screen_and_log("ERROR: Missing node netconf_temp_file", "cmapplynet:");
   }
   if (! $ip_version) {
       $sgeasyTool->screen_and_log("ERROR: Missing ip version flag", "cmapplynet:");
   }
   if (! $tempFilesRef) {
       $sgeasyTool->screen_and_log("ERROR: Missing temporary netconf files", "cmapplynet:");
   }
   exit (1) if (! $netconf_temp_file || ! $ip_version || ! $tempFilesRef);
   my @tempNetconfFiles = @$tempFilesRef;

   my @definedPrimaryIPv6 = ();
   my $highestPrimaryIPv6Index = 0;
   my @interfaces = ();
   my @ipaddresses = ();
   my @netmasksOrPrefixlens = ();
   my %netconfParsedData = ();
   my $j = 0;

   my $temp_netconf_file = "/tmp/temp_netconf_file" . "$$";
   `sh -x $netconf_temp_file > $temp_netconf_file 2>&1`;
   my @VARIABLES_CONF=`cat $temp_netconf_file`;
   unlink($temp_netconf_file);
   my $highest_index = my $index = 0;

   if ($ip_version eq "ipv4") {
       @interfaces = grep{$_ =~ m/INTERFACE_NAME\[\d+\]=[a-zA-Z0-9]+/}@VARIABLES_CONF; 
   }
   elsif ($ip_version eq "ipv6") {
       @interfaces = grep{$_ =~ m/IPV6_SECONDARY_INTERFACE_NAME/}@VARIABLES_CONF;
       @definedPrimaryIPv6 = grep{$_ =~ m/IPV6_INTERFACE\[\d+\]=[a-zA-Z0-9]+/}@VARIABLES_CONF; 
   }

   foreach my $i (0 .. $#definedPrimaryIPv6) {
       my $primaryIPv6Int;
         
      if ($definedPrimaryIPv6[$i] =~ /.*\[(\d+)\]=(\S+)/) { 
           $definedPrimaryIPv6[$i] = $2;
           if ($1 > $highestPrimaryIPv6Index) {
               $highestPrimaryIPv6Index = $1;
           }
       } 
   }
 
   foreach (@interfaces) {
     my $ipIndex = -1;
     my $netmPrefixIndex = -1;
     my ($ip_value, $netmaskOrprefixlen_value, $ip_name, $netmaskOrprefixlen_name);
     
     my ($interface_name, $interface_value) = split('=', $_, 2);
     if ($interface_name =~ m/\[(\d+)\]/) {
         $index = $1;
         $highest_index = $1 if ($1 > $highest_index);

         # Find the IP address for this network interface
         if ($ip_version eq "ipv4") {
             @ipaddresses = grep{$_ =~ m/IP_ADDRESS/}@VARIABLES_CONF;
         }
         elsif ($ip_version eq "ipv6") {
             @ipaddresses = grep{$_ =~ m/IPV6_ADDRESS/}@VARIABLES_CONF;
         }
         foreach (@ipaddresses) {
             ($ip_name, $ip_value) = split('=', $_, 2);
             if ($ip_name =~ m/\[(\d+)\]/) {
                 if ($index == $1) {
                     $ipIndex = $1;
                     last;
                 }
             }
             else {
                 cleanup_temp_files(@tempNetconfFiles);
                 $sgeasyTool->screen_and_log("ERROR: Index missing for IP $ip_value in $netconf_temp_file",
                                             "cmapplynet:");
                 exit(1);
             }
         }
         if ($index != $ipIndex) {
             cleanup_temp_files(@tempNetconfFiles);
             $sgeasyTool->screen_and_log("ERROR: $interface_value does not have an IP entry in " .
                                         "$netconf_temp_file", "cmapplynet:");
             exit(1);
         }

         # Find the netmask or prefixlen for this network interface
         if ($ip_version eq "ipv4") {
             @netmasksOrPrefixlens = grep{$_ =~ m/SUBNET_MASK/}@VARIABLES_CONF;
         }
         elsif ($ip_version eq "ipv6") {
             @netmasksOrPrefixlens = grep{$_ =~ m/IPV6_PREFIXLEN/}@VARIABLES_CONF;
         }
         foreach (@netmasksOrPrefixlens) {
             ($netmaskOrprefixlen_name, $netmaskOrprefixlen_value) = split('=', $_, 2);
             if ($netmaskOrprefixlen_name =~ m/\[(\d+)\]/) {
                 if ($index == $1) {
                     $netmPrefixIndex = $1;
                     last;
                 }
             }
             else {
                 cleanup_temp_files(@tempNetconfFiles);
                 $sgeasyTool->screen_and_log("ERROR: Index missing for netmask or prefixlen " .
                                             "$netmaskOrprefixlen_value in $netconf_temp_file",
                                             "cmapplynet:");
                 exit(1);
             }
         }
         if ($index != $netmPrefixIndex) {
             cleanup_temp_files(@tempNetconfFiles);
             $sgeasyTool->screen_and_log("ERROR: $interface_value does not have subnet mask entry " .
                                         "in $netconf_temp_file", "cmapplynet:")
                                                                   if ($ip_version eq "ipv4");
             $sgeasyTool->screen_and_log("ERROR: $interface_value does not have prefixlen entry " .
                                         "in $netconf_temp_file", "cmapplynet:")
                                                                   if ($ip_version eq "ipv6");
             exit(1);
         }

         # this is to right trim "lan1  "
         $interface_value = rtrim($interface_value);
         $ip_value = rtrim($ip_value);
         $netmaskOrprefixlen_value = rtrim($netmaskOrprefixlen_value);
         @{$netconfParsedData{$ip_version}[$j]} = 
                             ($interface_value, $ip_value, $netmaskOrprefixlen_value);
         $j = $j + 1;
     }
     else {
            cleanup_temp_files(@tempNetconfFiles);
            $sgeasyTool->screen_and_log("ERROR: Index missing for interface $interface_value in " .
                                        "$netconf_temp_file", "cmapplynet:");
            exit(1);
     }
   }

   return ($highest_index, $highestPrimaryIPv6Index, \%netconfParsedData, \@definedPrimaryIPv6);
}

sub check_syntax_netconf {
    my (@tempFiles) = @_;
    if (! @tempFiles) {
        $sgeasyTool->screen_and_log("ERROR: Missing temporary netconf(v4/v6) files",
                                    "cmapplynet:");
        exit(1);
    }

    foreach my $temp_file (@tempFiles) {
       my $num_changed_file = chmod 744, $temp_file;
       if ($num_changed_file != 1) {
           cleanup_temp_files(@tempFiles);
           $sgeasyTool->screen_and_log("ERROR: Failed to chmod file $temp_file", "cmapplynet:");
           exit(1);
       }
       `$temp_file`;
       my $exit_code = `echo $?`;
       if ($exit_code != 0) {
           cleanup_temp_files(@tempFiles);
           $sgeasyTool->screen_and_log("ERROR: Netconf file $temp_file contains syntax errors",
                                       "cmapplynet:");
           exit(1);
       }
    }
}

sub cleanup_temp_files {
    my (@tempFiles) = @_;
    if (! @tempFiles) {
        $sgeasyTool->screen_and_log("ERROR: Missing tempFiles", "cmapplynet:");
        exit(1);
    }

    foreach my $temp_file (@tempFiles) {
       unlink($temp_file) if (-f $temp_file);
    }
}

sub cmcp {
    my ($src_node, $src, $dst_node, $dst) = @_;

    if ($src_node eq $hostname && $src_node eq $dst_node) {
       if (copy($src, $dst) == 1) {
           return 0;
       }
       return 1;
    } else {
        my $cmd = "cmcp $src_node:$src $dst_node:$dst";
        qx/$cmd/;
        return $?;
    }
}

sub setup_logging {
    $sgeasyTool = new EDTools("cmapplynet");
    if ($preview) {
        $sgeasyTool->screen_and_syslog("Previewing cmapplynet");
    } else {
        $sgeasyTool->screen_and_syslog("Running cmapplynet");
        $sgeasyTool->screen_and_syslog("Saving command output to $sgeasyTool->{logfile}");
        $sgeasyTool->screen_and_log("Running cmapplynet",
                                    "cmapplynet:");
    }
}

sub cmexec {
    my ($node, $cmd) = @_;
    my $CMEXEC = undef;
    $CMEXEC = $SGBIN."cmexec";
    if ($node eq $hostname) {
        return qx/$cmd/
    } else {
        return qx/$CMEXEC $node $cmd/;
    }
}

sub expand_ipv6_addr {

    my ($addr) = @_;
  
    my $expanded_addr;
    my $return_addr = "";

    # split address at the :: and add necessary sections of zeroes
    # to remove the shorthand notation
    if ($addr =~ /::/) {
        my @presuff = split(/::/, $addr);
        my @sections = split(/:/, $presuff[0]);
        my $sections = scalar(@sections);
        @sections = split(/:/, $presuff[1]);
        $sections += scalar(@sections);
       
        for ($sections .. 7) {
            $presuff[0] = "$presuff[0]:0";
        }

        $expanded_addr = "$presuff[0]:$presuff[1]";
    }
    else {
        $expanded_addr = $addr;
    }

    # convert to decimal and then back to hex to remove any discrepencies in
    # leading zeroes and to convert case of hex lettering
    my @addr_sections = split(/:/, $expanded_addr);
    foreach my $i (0 .. $#addr_sections) {
        $addr_sections[$i] = hex($addr_sections[$i]);
        $addr_sections[$i] = sprintf("%04x", $addr_sections[$i]);
    }

    $return_addr = join(":", @addr_sections); 

    return $return_addr;
}

sub get_ipv6_subnet {

    my ($addr, $prefix_len) = @_;

    # split the address into eight hex strings of 16 bits
    my @subnet = split(/:/,$addr); 
    my @subnet_doubles = (0,0,0,0);

    # break the 128 bit address into four 32-bit numbers
    foreach my $i (0..3) {
        $subnet_doubles[$i] = hex($subnet[2*$i]);
        $subnet_doubles[$i] = ($subnet_doubles[$i] << 16);
        $subnet_doubles[$i] += hex($subnet[2*$i+1]);
    }

    # apply the bit mask (ipv6 prefix)
    my $complete_doubles = int($prefix_len/32);
    my $partial_bits = $prefix_len%32;

    # remove the masked bits by shifting them off and then
    # shifting in zeroes to replace them
    if ($partial_bits == 0) {

        # NOTE: Due to the way perl optimizes bit shifts, if you shift 
        # 32 places to the right, you end up with the same number.
        # However, if you shift up to 31 places to the right, you shift in 
        # zeroes. The same is true for left shifts.

        $subnet_doubles[$complete_doubles] = 0;
    }
    else {
        my $shift_bits = 32 - $partial_bits;
        $subnet_doubles[$complete_doubles] = 
                   ($subnet_doubles[$complete_doubles] >> $shift_bits);
        $subnet_doubles[$complete_doubles] = 
                   ($subnet_doubles[$complete_doubles] << $shift_bits); 
    }

    # clear the remaining doubles
    for my $i (($complete_doubles + 1) .. 3) {
        $subnet_doubles[$i] = 0;
    }
    
    # convert the four 32 bit decimals into a string representing the 
    # ipv6 subnet in hex
    my $ipv6_subnet_str = sprintf("%08x%08x%08x%08x", $subnet_doubles[0], 
                                  $subnet_doubles[1], $subnet_doubles[2],
                                  $subnet_doubles[3]); 
    return $ipv6_subnet_str;    
} 


sub validate_template_against_self {

    my ($v4_lans, $v6_lans) = @_;
    my $ret = 0;
    my @v4_lans = @{$v4_lans};
    my @v6_lans = @{$v6_lans};
    my %seen_intf = ();
    my %seen_ip = ();
    my %clan_ipv4_subnets = ();
    my %clan_ipv6_site_subnets = ();
    my %clan_ipv6_global_subnets = ();

    for my $clan (@v4_lans) {
        my %clan = %{$clan};

        my $nodes = $clan{'node_list'};
        my @nodes = @{$nodes};
        my $current_index = $clan{'connected_lan'};
        my %seen_subnet = ();
        my @all_nodes = ();

        for my $node (@nodes) {
            my %node = %{$node};
            my $node_name = $node{'node_name'};
            my $intfs = $node{'intf_list'};
            my @intfs = @{$intfs};
            my $ips_confd = 0;

            push @all_nodes, $node_name;

            for my $intf (@intfs) {
                my %intf = %{$intf};
                my $ip;
                my $mask;

                if ($intf{'ip_address'}) {
                    $ip = $intf{'ip_address'};
                    if ($seen_ip{$ip}++) {
                        $sgeasyTool->screen_and_log("ERROR: IP address $ip is assigned to multiple " .
                                                    "network interfaces.", "cmapplynet:");
                        $ret++;
                    }
                    
                    if (!$intf{'subnet_mask'}) {
                        $sgeasyTool->screen_and_log("ERROR: Subnet mask must be defined for " .
                                                    "configured IP address $intf{'ip_address'}", 
                                                    "cmapplynet:");
                        $ret++;
                    } 
                    else {
                        my @subnet = (0,0,0,0);

                        unless ($ip =~ /^([0-9]{1,3}.){3}[0-9]{1,3}$/) {
                            $sgeasyTool->screen_and_log("ERROR: Malformed IP address $ip.",
                                                        "cmapplynet:");
                            $ret++;
                        }

                        $mask = $intf{'subnet_mask'};

                        unless ($mask =~ /^([0-9]{1,3}.){3}[0-9]{1,3}$/) {
                            $sgeasyTool->screen_and_log("ERROR: Malformed subnet mask $mask.",
                                                        "cmapplynet:");
                            $ret++;
                        }

                        my @addr_octets = split(/\./, $ip);
                        my @mask_octets = split(/\./, $mask);

                        my $hex_ip = 0;
                        $hex_ip = ($hex_ip << 8) | $_ foreach @addr_octets;
                        my $hex_mask = 0;
                        $hex_mask = ($hex_mask << 8) | $_ foreach @mask_octets;

                        # No duplicate subnets in each node.
                        my $subnet = $hex_ip & $hex_mask;
                        if ($seen_subnet{$node_name}{$subnet}++) {
                            $sgeasyTool->screen_and_log("ERROR: Duplicate subnet $subnet for the same node.",
                                                        "cmapplynet:");
                            $ret++;
                        }

                        # No duplicate subnets across bridged nets.
                        foreach my $cnetnum (keys %clan_ipv4_subnets) {
                            next if ($cnetnum eq $current_index);
                            if (($clan_ipv4_subnets{$cnetnum}{$subnet}) and 
                                ($clan_ipv4_subnets{$cnetnum}{$subnet} eq $subnet)) {
                                $sgeasyTool->screen_and_log("ERROR: Subnet $subnet defined for IP " .
                                                            "address $ip is also defined for " .
                                                            "bridged net $cnetnum.", "cmapplynet:");
                                $ret++;
                            }
                        }
                        $clan_ipv4_subnets{$clan{'connected_lan'}}{$subnet} = $subnet;
                    }

                } 
    
                if ($seen_intf{$node{'node_name'}}{$intf{'intf_name'}}++) {
                    $sgeasyTool->screen_and_log("ERROR: Interface $intf{'intf_name'} is defined " .
                                                "more than once for node $node{'node_name'}",
                                                "cmapplynet:");
                    $ret++;
                } 

            } # foreach $intf

        }  # foreach $node

        # Verify that every ipv4 subnet is configured on all the nodes.
        my @configured_nodes = ();
        foreach my $my_node (keys %seen_subnet) {
            push @configured_nodes, $my_node;
            foreach my $subnet (keys %{$seen_subnet{$my_node}}) {
                foreach my $other_node (keys %seen_subnet) {
                    next if ($my_node eq $other_node);
                    if (!$seen_subnet{$other_node}{$subnet}) {
                        $sgeasyTool->screen_and_log("ERROR: subnet $subnet is configured on " .
                              "$my_node, but not on $other_node", "cmapplynet:");
                        $ret++;
                    }
                }
            }
        }

        if (@configured_nodes and (@configured_nodes != @all_nodes)) {
            $sgeasyTool->screen_and_log("ERROR: Asymmetric networks are not supported by this command. " .
                                        "Subnet on bridged net $current_index is configured on " .
                                        "@configured_nodes, not on all the nodes @all_nodes", 
                                        "cmapplynet:");
            $ret++;
        }

    } # foreach $clan


    for my $clan (@v6_lans) {
        my %clan = %{$clan};

        my $nodes = $clan{'node_list'};
        my @nodes = @{$nodes};
        my %seen_global_subnet = ();
        my %seen_site_subnet = ();
        my $current_index = $clan{'connected_lan'};
        my @all_nodes = ();

        for my $node (@nodes) {
            my %node = %{$node};
            my $node_name = $node{'node_name'};
            my $intfs = $node{'intf_list'};
            my @intfs = @{$intfs};

            push @all_nodes, $node_name;

            for my $intf (@intfs) {
                my %intf = %{$intf};
                my $ip;
                my $expanded_ip;
                my $ipv6_subnet;
                my $prefix_len = 64;
    
                if ($intf{'ipv6_prefixlen'}) {
                    if (($intf{'ipv6_prefixlen'} < 0) 
                        || ($intf{'ipv6_prefixlen'} > 127)) {
                        $sgeasyTool->screen_and_log("ERROR: Invalid prefix length entered for " .
                                                    "interface $intf{'intf_name'} on node " .
                                                    "$node{'node_name'}. Prefix length must be".
                                                    " a number between 0 and 127.", "cmapplynet:");
                        $ret++;
                    } 
                    else {
                        $prefix_len = $intf{'ipv6_prefixlen'};
                    }
                }

                if ($intf{'ip6_global_addr'}) {
                    $ip = $intf{'ip6_global_addr'};
                    $expanded_ip = expand_ipv6_addr($ip);

                    unless ($expanded_ip =~ 
                            /^([0-9a-fA-F]{1,4})(\:([0-9a-fA-F]{1,4})){7}$/) {
                        $sgeasyTool->screen_and_log("ERROR: Malformed IP address $ip.", 
                                                    "cmapplynet:");
                        $ret++;
                        last;
                    }

                    $ipv6_subnet = get_ipv6_subnet($expanded_ip, $prefix_len);

                    if ($seen_ip{$expanded_ip}++) {
                        $sgeasyTool->screen_and_log("ERROR: IP address $ip is assigned to multiple " .
                                                    "network interfaces.", "cmapplynet:");
                        $ret++;
                    }

                    # No duplicate global subnets in each node.
                    if ($seen_global_subnet{$node_name}{$ipv6_subnet}++) {
                        $sgeasyTool->screen_and_log("ERROR: Duplicate global subnet $ipv6_subnet for the same node.", "cmapplynet:");
                        $ret++;
                    }

                    # No duplicate global subnets across bridged nets.
                    foreach my $cnetnum (keys %clan_ipv6_global_subnets) {
                         next if ($cnetnum eq $clan{'connected_lan'});
                         if (($clan_ipv6_global_subnets{$cnetnum}{$ipv6_subnet}) and
                             ($clan_ipv6_global_subnets{$cnetnum}{$ipv6_subnet} eq $ipv6_subnet)) {
                             $sgeasyTool->screen_and_log("ERROR: Subnet defined for IP " .
                                                         "address $ip is also defined for " .
                                                         "bridged net $cnetnum.", "cmapplynet:");
                             $ret++;
                         }
                     }
                     $clan_ipv6_global_subnets{$clan{'connected_lan'}}{$ipv6_subnet} 
                                                                = $ipv6_subnet;

                } 

                if ($intf{'ip6_site_addr'}) {
                    $ip = $intf{'ip6_site_addr'};
                    $expanded_ip = expand_ipv6_addr($ip);

                    unless ($expanded_ip =~ 
                            /^([0-9a-fA-F]{1,4})(\:([0-9a-fA-F]{1,4})){7}$/) {
                        $sgeasyTool->screen_and_log("ERROR: Malformed IP address $ip.", "cmapplynet:");
                        $ret++;
                        last;
                    }

                    $ipv6_subnet = get_ipv6_subnet($expanded_ip, $prefix_len);

                    if ($seen_ip{$expanded_ip}++) {
                        $sgeasyTool->screen_and_log("ERROR: IP address $ip is assigned to multiple " .
                                                    "network interfaces.", "cmapplynet:");
                        $ret++;
                    }

                    # No duplicate site subnets in each node.
                    if ($seen_site_subnet{$node_name}{$ipv6_subnet}++) {
                        $sgeasyTool->screen_and_log("ERROR: Duplicate site-local subnet $ipv6_subnet for the same node.", "cmapplynet:");
                        $ret++;
                    }

                    # No duplicate global subnets across bridged nets.
                    foreach my $cnetnum (keys %clan_ipv6_global_subnets) {
                         next if ($cnetnum eq $clan{'connected_lan'});
                         if (($clan_ipv6_site_subnets{$cnetnum}{$ipv6_subnet}) and
                             ($clan_ipv6_site_subnets{$cnetnum}{$ipv6_subnet} eq $ipv6_subnet)) {
                             $sgeasyTool->screen_and_log("ERROR: Subnet defined for IP " .
                                                         "address $ip is also defined for " .
                                                         "bridged net $cnetnum.", "cmapplynet:");
                             $ret++;
                         }
                    }
                } 


                if ($seen_intf{$node{'node_name'}}{$intf{'intf_name'}}++) {
                    $sgeasyTool->screen_and_log("ERROR: Interface $intf{'intf_name'} is defined " .
                                                "more than once for node $node{'node_name'}", "cmapplynet:");
                    $ret++;
                }

            } # foreach $intf

        }  # foreach $node

        # Verify that every ipv6 global subnet is configured on all the nodes.
        my @configured_nodes = ();
        foreach my $my_node (keys %seen_global_subnet) {
            push @configured_nodes, $my_node;
            foreach my $global_ipv6_subnet (keys %{$seen_global_subnet{$my_node} }) {
                foreach my $other_node (keys %seen_global_subnet) {
                    next if ($my_node eq $other_node);
                    if (!$seen_global_subnet{$other_node}{$global_ipv6_subnet}) {
                        $sgeasyTool->screen_and_log("ERROR: ipv6 global subnet $global_ipv6_subnet is " .
                                                    "configured on $my_node, but not on $other_node", 
                                                    "cmapplynet:");
                        $ret++;
                    }
                }
            }
        }

        if (@configured_nodes and (@configured_nodes != @all_nodes)) {
            $sgeasyTool->screen_and_log("ERROR: Asynmetric network is not supported. " .
                                        "Global subnet on bridged net $current_index is configured on " .
                                        "@configured_nodes, not on all the nodes @all_nodes", 
                                        "cmapplynet:");
            $ret++;
        }

        # Verify that every ipv6 site subnet is configured on all the nodes.
        @configured_nodes = ();
        foreach my $my_node (keys %seen_site_subnet) {
            push @configured_nodes, $my_node;
            foreach my $site_ipv6_subnet (keys %{$seen_site_subnet{$my_node}}) {
                foreach my $other_node (keys %seen_site_subnet) {
                    next if ($my_node eq $other_node);
                    if (!$seen_site_subnet{$other_node}{$site_ipv6_subnet}) {
                        $sgeasyTool->screen_and_log("ERROR: ipv6 site subnet $site_ipv6_subnet is " . 
                                                    "configured on $my_node, but not on $other_node", 
                                                    "cmapplynet:");
                        $ret++;
                    }
                }
            }
        }

        if (@configured_nodes and (@configured_nodes != @all_nodes)) {
            $sgeasyTool->screen_and_log("ERROR: Asynmetric network is not supported. " .
                                        "Site subnet on bridged net $current_index is configured on " .
                                        "@configured_nodes, not on all the nodes @all_nodes", 
                                        "cmapplynet:");
            $ret++;
        }

    } # foreach $clan

    return $ret;
}

# All the nodes should be on the same SG version 11.20 or higher.
sub validate_sg_version {
    my ($nodes_ref) = @_;
    my @nodes = @$nodes_ref;
    my $sg_version;

    foreach my $node_name (@nodes) {
        my $version = cmexec($node_name, $SGBIN."cmversion 2> /dev/null"); 
        if ($? != 0) {
            fail "Serviceguard is not installed on $node_name.";
        }
        if (!$sg_version) {
            $sg_version = $version;
            my @sgversion = split /\./, $sg_version;
            if ($sgversion[1] <= 11 && $sgversion[2] < 20) {
                    fail "$node_name must have serviceguard version A.11.20.00 or higher";
            }
        }
        elsif ($version ne $sg_version) {
            fail "All the nodes should have the same version of the serviceguard.";
        
        }
    }
}

# Convert netmask from Hexadecimal to Dotted decimal
sub netmask_hex_to_decimal_dot {
    my ($netconf_netmOrpref) = @_;

    my($x1,$x2,$x3,$x4,$x5,$x6,$x7,$x8,$d1,$d2,$d3,$d4) = '';
    $netconf_netmOrpref = substr($netconf_netmOrpref, 2)
                          if ($netconf_netmOrpref =~ m/^0x/);
    ($x1,$x2,$x3,$x4,$x5,$x6,$x7,$x8)=split(//,$netconf_netmOrpref);
    my $h4=$x1.$x2; $d4=hex($h4);
    my $h3=$x3.$x4; $d3=hex($h3);
    my $h2=$x5.$x6; $d2=hex($h2);
    my $h1=$x7.$x8; $d1=hex($h1);
    return "$d4\.$d3\.$d2\.$d1";
}

sub update_network_script_files {
     my $network_script = "";
     my @host = split(/\./, $hostname);
     my $remotehost;

     #print Dumper %compareEntryToNetconf; 
     foreach my $node (keys %compareEntryToNetconf) {
         $remotehost = 0;
         foreach my $ip_version (keys %{$compareEntryToNetconf{$node}}) {
             for my $i ( 0 .. $#{ $compareEntryToNetconf{$node}->{$ip_version} } ) {
                 my ($intf_nm, $ip_addr, $netmaskOrprefixlen, $new) =
                          @{$compareEntryToNetconf{$node}->{$ip_version}[$i]};
                 if ($intf_nm eq 'eth0') { next; }
                 unless ($intf_nm =~ m/:/) {
                     $network_script = $NTWRK_SCRIPT . "ifcfg-" . $intf_nm;
                     backup_network_script($node, $network_script);
                     if ($node ne $host[0]) {
                         $network_script = $NTWRK_SCRIPT . "ifcfg-" . $intf_nm . "." . $node;
                         $remotehost = 1;
                     }
                     print "Updating network file : $network_script\n" if ($debug);
                     open(NETWORK_FILE, ">$network_script") or fail "Failed to open $network_script";
                     print NETWORK_FILE "DEVICE=$intf_nm\n";
                     print NETWORK_FILE "IPADDR=$ip_addr\n"; 
                     print NETWORK_FILE "NETMASK=$netmaskOrprefixlen\n";
                     if (-f "/etc/redhat-release") {
                         print NETWORK_FILE "ONBOOT=yes\n";
                     } else {
                         print NETWORK_FILE "STARTMODE=auto\n";
                     }
                     print NETWORK_FILE "BOOTPROTO=static\n";
                     print NETWORK_FILE "HWADDR=".interface_mac_addr($node, $intf_nm)."\n";
                     print NETWORK_FILE "TYPE=Ethernet\n";
                 }    
                 elsif ($intf_nm =~ m/:/ && $netmaskOrprefixlen == 64) {
                     my @primary_interface = split(":", $intf_nm);
                     $network_script = $NTWRK_SCRIPT . "ifcfg-" . $primary_interface[0];
                     backup_network_script($node, $network_script);
                     if ($node ne $host[0]) {
                         $network_script = $NTWRK_SCRIPT . "ifcfg-" . $primary_interface[0] . "." . $node;
                         $remotehost = 1;
                     }
                     print "Updating primary network file : $network_script\n" if ($debug); 
                     open(NETWORK_FILE, ">$network_script") or fail "Failed to open $network_script";
                     print NETWORK_FILE "IPV6INIT=yes\n";
                     print NETWORK_FILE "IPV6ADDR=\"$ip_addr\"\n";
                     print NETWORK_FILE "IPV6_MTU=1500\n";
                }
                close NETWORK_FILE;
                if ($remotehost == 1) {
                    # Copying network script to respective node
                    my @remote_network_script = split(/\./,$network_script);
                    my $CMCP = $SGBIN."cmcp";
                    `$CMCP $network_script $node:$remote_network_script[0]`; 
                    if ($? != 0) {
                        $sgeasyTool->screen_and_log("ERROR: Failed to copy network script file $network_script " .
                                         "to node $node", "cmapplynet:");
                        exit(1);
                    }
                    print "Copied $network_script from $hostname to $node:$remote_network_script[0]\n\n\n" if ($debug);
                    `rm -f $network_script`;
                }
            }
        }
        $sgeasyTool->screen_and_log("Updated network scripts on $node...[DONE]", "cmapplynet:");
    }
}

sub interface_mac_addr {
    my ($node, $interface) = @_;
    my $addr = "ip link show $interface | awk '/ether/ {print \$2}'";
    #print "Address = $addr\n";
    my $mac_addr = cmexec($node, $addr);
    chomp($mac_addr);
    print "$node: $interface $mac_addr\n" if ($debug);
    return uc($mac_addr);
}

sub backup_network_script {
     my ($node, $network_script) = @_;
     my $network_bkp_file = "";
     my $i = 0;
     my $cmd = "ls $network_script  > /dev/null 2>&1";
     cmexec($node, $cmd);
     if ($? == 0) {
         my @array = split('/', $network_script);
         my $count = @array;
         my @file = split('-',$array[$count-1]);
         for ($i = 0; $i < ($count - 1); $i++) {
             $network_bkp_file = $network_bkp_file . $array[$i] . "/";
         }
         $network_bkp_file = $network_bkp_file . $file[1] . "-" . $file[0] . ".bkp";
         my $cmd = "mv $network_script $network_bkp_file";
         cmexec($node,$cmd);
         if ($? != 0) {
            $sgeasyTool->screen_and_log("Failed to backup network script $network_script on node $node", "cmapplynet:");
            exit(1);
        }
     }
}

