#!/usr/bin/perl -w
##################################################################
# (C) Copyright 2013-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 Getopt::Long qw(GetOptions HelpMessage :config no_ignore_case);
use Pod::Usage;
use Data::Dumper;

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

my @file_out = undef;
my $sgeasylib = undef;

BEGIN {
    my $sgconffile = "/etc/cmcluster.conf";
    if (-f $sgconffile) {
        @file_out = qx(cat $sgconffile);
    } else {
        print STDERR "ERROR: Unable to run cmquerynet ".
                     "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;

=head1 NAME

cmquerynet   -  Probes Network connectivity across the Nodes and generates Network template.

Example  :  cmquerynet -N network_template -n node1 -n node2

=cut

# only root can run this
unless ($< == 0) {
    fail("Only root is allowed to run $0\n");
}

# process argument
# -n <node>      name of node to include. If none, local host
# -N <network_template>         configure cluster network

=head1 SYNOPSIS

cmquerynet
S<[-n node]...>
S<[-N network_template]>

=cut

# Data structure for network probed data.

# Network object holds the detailed information of all nodes in a cluster 
package Network;

sub new {
    my $self = { };
    $self->{nodes}=[]; # Node objects 
    bless $self;
    return $self;
}

sub addNode {
    my ($self, $node) = @_;
    push(@{$self->{nodes}}, $node);
}

# Node object holds the detailed information of all it's interfaces 
package Node;

sub new {
    my $self = { };
    $self->{name}="";
    $self->{interfaces}=[]; # Interfaces in a node
    $self->{node_id} = 0;
    bless $self;
    return $self;
}

sub addInterface {
    my ($self, $interface) = @_;
    push(@{$self->{interfaces}}, $interface);
}

# Interface holds the detailed information of itself
package Interface;

sub new {

    my $self = {};
    $self->{name} = "";
    $self->{bridgeNet} = 0;
    $self->{mac} = "";
    $self->{ipaddress} = "";
    $self->{subnet_mask} = "";
    #$self->{ipv6Addresses} = "";
    $self->{connected_interface} = []; # Connected interfaces from this interface
    $self->{visited} = 'n';
    bless $self;
    return $self;
}

sub addConnectedInterface {
    my ($self, $connectedInterface) = @_;
    push(@{$self->{connected_interface}}, $connectedInterface);
}

package ConnectedInterface;

sub new {
    my $self = {};
    $self->{node_name} = "";
    $self->{interface} = "";
    bless $self;
    return $self;
}

# BridgeNet object holds the list of connected interfaces.
# This gets populated when we start traversing connectivity path from 
# 1st interface till we find the bridgenet for that interface.
# For next interface and other bridgenet path it is re-initialised.

package BridgeNet;

sub new {
    my $self = {};
    $self->{bridgeNet} = [];
    bless $self;
    return $self;
}

sub addBridgeNet {
    my ($self, $bridgeNet) = @_;
    push(@{$self->{bridgeNet}}, $bridgeNet);
}

package BridgeNetList;

sub new {
    my $self = {};
    $self->{node_name} = "";
    $self->{interface_name} = "";
    bless $self;
    return $self;
}

package main;

# GLOBALS
my @nodes;
my $network_layout_file;
my $arg_errors = 0;
my $sgeasyTool;
my $bridgeNet_id = 0;
my @bridgeNet_list;
my $bond = 0;
my $debug = 0;
my $SGCONF = undef;
my $SGBIN = undef;

my $sgconffile = "/etc/cmcluster.conf";
if (-f $sgconffile) {
    @file_out = qx(cat $sgconffile);
} else {
    print STDERR "ERROR: Unable to run cmquerynet ".
                 "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]."/";

# Full path to "network_probe_query" application.
my $NETWORK_PROBE_QUERY = $SGBIN . "network_probe_query"; 
#Full path to "network_probe_reply" application.
my $NETWORK_PROBE_REPLY = $SGBIN . "network_probe_reply";
my $cmexec = $SGBIN . "cmexec"; # Full path to "cmexec"
my $networkObj = Network->new();
my $bridgeNetObj;

#######################################################

exit (1) if  (parse_args());

# Initiate logging.
setup_logging();

# Test whether cmexec to remote node is successful. If not ask the
# user to run cmpreparecl, which does all the required things for
# the cmexec.
foreach my $node (@nodes) {
    if ($sgeasyTool->test_cmexec($node) != 0) {
        fail("ERROR:Authentication failed while using cmexec to $node." .
        "\nPlease ensure cmexec for $node works.");
    }
}

# Get the interfaces and their corresponding IP, SUBNET and MAC addresses
# from all given @nodes 
get_interface_info();
$sgeasyTool->screen_and_log("Gathered network information from all nodes.","cmquerynet:");

# with their MAC addresses. Let's check connectivity among them.
# Probe the connectivity by sending LLC packets through network_probe_query utility
$sgeasyTool->screen_and_log("Probing network connectivity...","cmquerynet:");
$sgeasyTool->screen_and_log("Wait it may take a while to finish the probing...","cmquerynet:");

if (@nodes == 1) {
    # Single node probing
    probe_local_interfaces_connectivity();
} else {
    probe_interfaces_connectivity();
}
 
$sgeasyTool->screen_and_log("Completed probing.","cmquerynet:");

print_connected_interfaces() if ($debug);
if (@nodes > 1) {
    get_bridgenets();
}

print Dumper $networkObj if ($debug);

# Using filled data structure let's generate the network template
create_network_template();
$sgeasyTool->screen_and_log("Created network template: $network_layout_file","cmquerynet:");
$sgeasyTool->screen_and_log("[DONE]!","cmquerynet:");

exit(0);
#######################################################

 ## FUNCTION DEFINATIONS ##

 #********************************************************************************
 # FUNCTION - parse_arg 
 #
 #            Parse the command line arguments.
 #
 #********************************************************************************

sub parse_args {
    GetOptions(
        # these require an argument.
        "n|node=s"              => sub { parse_cmd_line_arg(@_, \@nodes) },
        "N|network_template=s"          => sub { parse_cmd_line_arg(@_, \$network_layout_file) },
        "h|help|?"              => sub { pod2usage(-verbose => 1) }
     ) || pod2usage();

     if ($arg_errors) {
         pod2usage();
         return 1;
     }
     unless (@nodes && $network_layout_file) {
         pod2usage();
         return 1;
     }
     if (@ARGV) {
         pod2usage("Unknown arguments on command line : " . join (" ", @ARGV));
         return 1;
     }
     return 0;
}

sub parse_cmd_line_arg {
    my ($name, $val, $target) = @_;
    if ($val =~ /^-/) {
        print "Option $name requires an argument\n";
        $arg_errors++;
    } else {
        if (ref($target) eq "ARRAY") {
            push @{$target}, $val;
        } elsif (ref($target) eq "SCALAR") {
            # a scalar target generally indicates an option that should not be
            # used more than once, so check for that.
            if (${$target}) {
                print "Option $name should not be supplied more than once\n";
                $arg_errors++;
            }
            ${$target} = $val;
        }
    }
}

 #********************************************************************************
 # FUNCTION - get_interface_info, get_interface_info_Suse and get_interface_info_Redhat
 #                               
 #        Retrieves all information (Interface Name, MAC address, IP addr, Mask)
 #        associated with the existing interfaces (whether UP or DOWN) from all 
 #        mentioned nodes.
 #         
 #        Populates the retrieved information to well structured data structure 
 #        networkObj.
 #
 #*********************************************************************************

sub get_interface_info_Redhat {
    my $nodeObj;
    my $interfaceObj;
    my $nodeId = 1; 
    foreach my $node (@nodes) {
       $nodeObj = Node->new();
       $nodeObj->{name} = $node;
       $nodeObj->{node_id} = $nodeId++; 
       $networkObj->addNode($nodeObj);
       my $eth;
       my @eths;
       my $eth_output;
       my @eth_output_array;
       my $ip = 0;
       my $mac = 0;
       my $subnet_mask = 0;

       @eths = `$cmexec $node ip addr`;
       if ($? != 0) {
           $sgeasyTool->screen_and_log("ERROR : cmexec failed to execute" .
                                       " ip addr on node $node", "cmquerynet:"); 
           exit(1);
       }
       for my $eth (@eths){
           #For Redhat7, the network interface name is eno<num> for LOM 
           # (example: eno33) and ens<num> for add-on cards (example: ens35)
           #For Redhat5 and Redhat6, the network interface name is eth<num>
           # (example: eth0)
           next if (!(($eth =~ /^\d+: en/) or ($eth =~ /^\d+: eth/)));
           $eth=(split ( /:/, $eth ))[1];
           $eth=~ s/^\s+|\s+$//g;
           chomp($eth);
           $eth_output = `$cmexec $node ip addr show dev $eth`;
           if ($? != 0) {
               $sgeasyTool->screen_and_log("ERROR : cmexec failed to execute" .
                                           "ip addr show dev $eth on $node", "cmquerynet:");
               exit(1);
           }
           @eth_output_array = split(/\n/,$eth_output);
           $ip = 0;
           $mac = 0; 
           $subnet_mask = 0;
           foreach my $ln (@eth_output_array) {
               if ($ln =~ /inet (.*) brd (.*)/) {
                   $ip = (split(/\//,$1))[0];
                   $subnet_mask = (split(/\=/,`ipcalc -m $1`))[1];
               }
               $mac = $1 if ($ln =~ /link\/ether\s+(\w+:\w+:\w+:\w+:\w+:\w+)\b/i);               
           }
           $interfaceObj = Interface->new();
           $interfaceObj->{name} = $eth;
           $interfaceObj->{mac} = $mac;
           $interfaceObj->{ipaddress} = $ip;
           $interfaceObj->{subnet_mask} = $subnet_mask;
           $nodeObj->addInterface($interfaceObj);
       }
    }
}

sub get_interface_info_Suse {
    my $nodeObj;
    my $interfaceObj;
    my $nodeId = 1; 
    foreach my $node (@nodes) {
       $nodeObj = Node->new();
       $nodeObj->{name} = $node;
       $nodeObj->{node_id} = $nodeId++; 
       $networkObj->addNode($nodeObj);
       my $eth;
       my @eths;
       my $eth_output;
       my @eth_output_array;
       my $ip = 0;
       my $mac = 0;
       my $subnet_mask = 0;

       @eths = `$cmexec $node /sbin/ifconfig -a`;
       if ($? != 0) {
           $sgeasyTool->screen_and_log("ERROR : cmexec failed to execute" .
                                       " /sbin/ifconfig -a on node $node", "cmquerynet:"); 
           exit(1);
       }
       for my $eth (@eths){
           next if (!($eth =~ /^eth/));
           $eth =~ s/\s.*$//;
           chomp($eth);
           $eth_output = `$cmexec $node /sbin/ifconfig $eth`;
           if ($? != 0) {
               $sgeasyTool->screen_and_log("ERROR : cmexec failed to execute" .
                                           " /sbin/ifconfig $eth on $node", "cmquerynet:");
               exit(1);
           }
           @eth_output_array = split(/\n/,$eth_output);
           $ip = 0;
           $mac = 0; 
           $subnet_mask = 0;
           foreach my $ln (@eth_output_array) {
               $ip = $1 if ($ln =~ /inet addr\s*:\s*(\d+\.\d+\.\d+\.\d+)\b/);
               $mac = $1 if ($ln =~ /HWaddr\s+(\w+:\w+:\w+:\w+:\w+:\w+)\b/i);
               $subnet_mask = $1 if ($ln =~ /Mask\s*:\s*(\d+\.\d+\.\d+\.\d+)\b/i);
           }

           $interfaceObj = Interface->new();
           $interfaceObj->{name} = $eth;
           $interfaceObj->{mac} = $mac;
           $interfaceObj->{ipaddress} = $ip;
           $interfaceObj->{subnet_mask} = $subnet_mask;
           $nodeObj->addInterface($interfaceObj);     
       }    
    }
}

sub get_interface_info {
       if ( -f "/etc/redhat-release" ) {
           get_interface_info_Redhat();
       } else {
           get_interface_info_Suse();
    }
}

 #********************************************************************************
 # FUNCTION - probe_local_interfaces_connectivity
 #
 #            Probe interfaces connectivity within node.
 #            Generate and assign bridgeNet_id to each and every probed interface
 #            as per connectivity.
 #
 #********************************************************************************

sub probe_local_interfaces_connectivity { 
    my $connectedInterfaceObj;
    my $interface_id = 0;
    my $no_of_interfaces = 0;
    foreach my $node (@{$networkObj->{nodes}}) {
        foreach my $source_interface (@{$node->{interfaces}}) {
             $interface_id++;
             if ($source_interface->{bridgeNet}) { next; }
             $bridgeNet_id++;
             # Set bridgenet id for last interface 
             $no_of_interfaces = (@{$node->{interfaces}});
             if (($interface_id == $no_of_interfaces) && (!$source_interface->{bridgeNet})) {
                 $source_interface->{bridgeNet} = $bridgeNet_id;
                 last;
             }
            `$cmexec $node->{name} /usr/bin/killall network_probe_reply 2>&1`;
            `$cmexec $node->{name} ip link set $source_interface->{name} up > /dev/null 2>&1`;
            if ($? != 0) {
                $sgeasyTool->screen_and_log("ERROR : cmexec failed to execute ip link set " . 
                                            "$source_interface->{name} up on $node->{name}", "cmquerynet:");
                exit(1);
            }
            # Identify destination interfaces.
            foreach my $destination_interface (@{$node->{interfaces}}) {
                if ($destination_interface->{bridgeNet}) { next; }
                if ($source_interface->{name} eq $destination_interface->{name}) { next; }
                `$cmexec $node->{name} ip link set $destination_interface->{name} up > /dev/null 2>&1`;
                if ($? != 0) {
                    $sgeasyTool->screen_and_log("ERROR : cmexec failed to execute ip link set " . 
                                                "$destination_interface->{name} up on $node->{name}", "cmquerynet:");
                    exit(1);
                }
                $sgeasyTool->screen_and_log("$cmexec $node->{name} $NETWORK_PROBE_REPLY" .
                                            " $destination_interface->{name}" ,
                                            "cmquerynet:") if ($debug);
                `$cmexec $node->{name} $NETWORK_PROBE_REPLY $destination_interface->{name}  > /dev/null &`;
                if ($? != 0) {
                    $sgeasyTool->screen_and_log("ERROR : cmexec failed to execute $NETWORK_PROBE_REPLY " .
                                                "$destination_interface->{name} > /dev/null &", "cmquerynet:");
                    exit(1);
                }
                # Run network_probe_query to check the interface connectivity.
                my $query_cmd = "$cmexec $node->{name} $NETWORK_PROBE_QUERY " . 
                                "$source_interface->{name} $destination_interface->{mac}";
                $sgeasyTool->screen_and_log("$cmexec $node->{name} $NETWORK_PROBE_QUERY " .
                                "$source_interface->{name} $destination_interface->{mac}" ,
                                "cmquerynet:") if ($debug);
                my @result = `$query_cmd`;
                if (@result) {
                    # my @output = split(" ", $result[0]);
                    # my @snode = split(/\./, $output[0]);
                    # print "$snode[0] $output[1] $output[2] $output[3]\n";
                    $source_interface->{bridgeNet} = $bridgeNet_id;
                    $destination_interface->{bridgeNet} = $bridgeNet_id;
                } else {
                    $source_interface->{bridgeNet} = $bridgeNet_id;
                }
                `$cmexec $node->{name} /usr/bin/killall network_probe_reply 2>&1`;
            }
        }
        last;
    }
}

 #********************************************************************************
 # FUNCTION - probe_interfaces_connectivity
 #
 #            Probe interfaces connectivity across all nodes.
 #            Generate and assign bridgeNet_id to each and every probed interface
 #            as per connectivity.
 #
 #********************************************************************************

sub probe_interfaces_connectivity {
    my @probed_nodes = ();
    my $probed = 0;
    my $connectedInterfaceObj;

    foreach my $source_node (@{$networkObj->{nodes}}) {
        # If we are probing on node whose node id is not 1,
        # kill all previously configured network_probe_reply.
        `$cmexec $source_node->{name} /usr/bin/killall network_probe_reply 2>&1`;

        # No need to probe for last node of a cluster.
        if ($source_node->{node_id} eq (@{$networkObj->{nodes}})) { last; } 
        foreach my $source_interface (@{$source_node->{interfaces}}) {
            `$cmexec $source_node->{name} ip link set $source_interface->{name} up > /dev/null 2>&1`;
            if ($? != 0) {
                $sgeasyTool->screen_and_log("ERROR : cmexec failed to execute ip link set " . 
                                            "$source_interface->{name} up on $source_node->{name}", "cmquerynet:");
                exit(1);
            }
            # Identify destination nodes for probing.
            foreach my $destination_node (@{$networkObj->{nodes}}) { 
                $probed = 0;
                if(@probed_nodes) {
                    foreach my $p_node (@probed_nodes) {
                        if ($destination_node->{name} eq $p_node) { $probed = 1; }
                    }
                }
                # Don't consider the already probed node (the node which is used as a source node earlier).
                if ($probed) { next; }; 
                if ($destination_node->{name} eq $source_node->{name}) { next; }
                # Identify destination interfaces. 
                foreach my $destination_interface (@{$destination_node->{interfaces}}) {
                    `$cmexec $destination_node->{name} ip link set $destination_interface->{name} up > /dev/null 2>&1`;
                    if ($? != 0) {
                        $sgeasyTool->screen_and_log("ERROR : cmexec failed to execute ip link set" .
                                                    "$destination_interface->{name} up on $destination_node->{name}", "cmquerynet:");
                        exit(1);
                    }
                    `$cmexec $destination_node->{name} $NETWORK_PROBE_REPLY $destination_interface->{name}  > /dev/null &`;
                    if ($? != 0) {
                        $sgeasyTool->screen_and_log("ERROR : cmexec failed to execute $NETWORK_PROBE_REPLY " . 
                                                    "$destination_interface->{name} > /dev/null &", "cmquerynet:");
                        exit(1);
                    }

                    $sgeasyTool->screen_and_log("$cmexec $source_node->{name} $NETWORK_PROBE_QUERY" . 
                                                " $source_interface->{name} $destination_interface->{mac}" ,
                                                "cmquerynet:") if ($debug);
                    # Run network_probe_query to check the interface connectivity.
                    my $query_cmd = "$cmexec $source_node->{name} $NETWORK_PROBE_QUERY " . 
                                    "$source_interface->{name} $destination_interface->{mac}";
                    my @result = `$query_cmd`;
                    if ($? != 0) {
                        $sgeasyTool->screen_and_log("ERROR : Communication failed between " .
                                                    "$source_node->{name} : $source_interface->{name} and " .
                                                    "$destination_node->{name} : $destination_interface->{name}", "cmquerynet:");
                        exit(1);
                    } 
                    if (@result) { # Interfaces are connected.
                        chomp(@result);
                        my @output = split(" ", $result[0]);
                        my @snode = split(/\./, $output[0]);
                        #print "$snode[0] $output[1] $output[2] $output[3]\n";
                        print "."; 
                        $connectedInterfaceObj = ConnectedInterface->new();
                        $connectedInterfaceObj->{node_name} = $destination_node->{name};
                        $connectedInterfaceObj->{interface} = $destination_interface->{name};
                        #$connectedInterfaceObj->{node_name} = $output[2];
                        #$connectedInterfaceObj->{interface} = $output[3]; 
                        $source_interface->addConnectedInterface($connectedInterfaceObj); 
                    } #else { print "No Connectivity\n"; }
                    `$cmexec $destination_node->{name} /usr/bin/killall network_probe_reply 2>&1`;
                }
             }
        }
        push(@probed_nodes, $source_node->{name}); # Make a list of nodes which are already probed.
        $sgeasyTool->screen_and_log("\nFinished probing on node : @probed_nodes .", "cmquerynet:");
    }
}

 #********************************************************************************
 # FUNCTION - create_network_template
 #
 #            Creates a network layout file using the information from data 
 #            structure $networkObj.
 #            
 #            $networkObj is a list of nodes which in turn has a list of all 
 #            interfaces populated with it's all information.
 #
 #********************************************************************************

sub create_network_template {
    my $count = 0;
    my $no_of_bridgenets = validate_no_of_bridgenets();
    open TEMPLATE , ">$network_layout_file" or fail($!);
    print TEMPLATE "# **********************************************************************\n";
    print TEMPLATE "# ********* HIGH AVAILABILITY CLUSTER NETWORK CONFIGURATION FILE *******\n";
    print TEMPLATE "# **********************************************************************\n\n";
    print TEMPLATE "# This file lists the minimum required parameters for creating a valid\n";
    print TEMPLATE "# network configuration across all the nodes in the cluster.\n";
    print TEMPLATE "# The user should fill this template as per their requirement and\n";
    print TEMPLATE "# hardware configuration, and follow the remaining steps in Easy\n";
    print TEMPLATE "# Deployment section of the \"Managing Serviceguard\" manual to\n";
    print TEMPLATE "# configure IP addresses.\n\n";
    print TEMPLATE "# If you are only using IPv6, skip this section and fill out the IPv6\n";
    print TEMPLATE "# section below, or vice versa\n\n";
    print TEMPLATE "# IPv4 Configuration\n";
    print TEMPLATE "# To configure IPv4 addresses fill-in relevant values as per\n";
    print TEMPLATE "# the template below and repeat as many times as necessary.\n";
    #print TEMPLATE "# IPv6 Configuration\n";
    #print TEMPLATE "# To configure IPv6 addresses fill-in relevant values as per\n";
    #print TEMPLATE "# the template below and repeat as many times as necessary.\n";
    print TEMPLATE "\n";

    if ($bridgeNet_id) {
        for ($count = 1; $count <= $no_of_bridgenets ; $count++) {
            print TEMPLATE "IPV4_BRIDGED_NET:$count # Discovered connected LAN interfaces.\n\n\n";
            foreach my $node (@{$networkObj->{nodes}}) {
                print TEMPLATE "    NODE: $node->{name}\n\n";
                foreach my $interface (@{$node->{interfaces}}) {
                    if ($interface->{bridgeNet} eq $count) {
                        print TEMPLATE "        # The following entries are pre-existing configured address\n";
                        print TEMPLATE "        # information.  Do not modify or delete the entries.\n\n";
                        print TEMPLATE "        INTERFACE_NAME: $interface->{name}\n";
                        if ($interface->{ipaddress}) {
                            print TEMPLATE "        IP_ADDRESS: $interface->{ipaddress}\n";
                        } else {
                            print TEMPLATE "        #IP_ADDRESS: \n";
                        }
                        if ($interface->{subnet_mask}) {
                            print TEMPLATE "        SUBNET_MASK: $interface->{subnet_mask}\n\n\n";
                        } else {
                            print TEMPLATE "        #SUBNET_MASK: \n\n\n";
                        }
                    }
                } 
            }
        }
    } else {
        $sgeasyTool->screen_and_log("ERROR : There is no Bridgenet configured" . 
                                    " in the cluster.", "cmquerynet:");
        exit(1);
    }
    close TEMPLATE; 
}

 #***********************************************************************************
 # FUNCTION - get_bridgenets
 #
 #            This function identifies the bridge across the interfaces in a cluster.
 #            It traverse recursively through data structure from lower node id to
 #            higher identifying the bridge.
 #
 #***********************************************************************************

sub get_bridgenets {
    my ($iNode, $iEth) = @_;
    my $bridgeNetListObj;
    my $traverse = 0;

    foreach my $node (@{$networkObj->{nodes}}) {
        if ($iNode) {
            if ($node->{name} ne $iNode) { next; }
        }
        foreach my $interface (@{$node->{interfaces}}) {
            if ($iEth) {
                if ($interface->{name} ne $iEth) { next; }
            }
            if ($node->{node_id} == 1) {
                if ($bond) {
                    $bridgeNet_id--;
                }
                $bond = 0;
                $bridgeNet_id++;
                $bridgeNetObj = undef;
                $bridgeNetObj = BridgeNet->new();
                $bridgeNetListObj = BridgeNetList->new();
                add_to_bridgenet_list($node->{name}, $interface->{name}, $bridgeNetListObj);
                if ($traverse) { # If structure is already traversed
                    # Resets the visited flag of all already visited interfaces to 'n'.
                    reset_visited_list_entry(); 
                }
            }
            if (!is_bridgenet_interface($node->{name}, $interface->{name})) {
                $sgeasyTool->screen_and_log("$node->{name} : $interface->{name} can't be part" .
                                            " of any bridgenet.", "cmquerynet:") if ($debug);
                if ($node->{node_id} == 1) {
                    $bridgeNet_id--;
                }
                next;
            }
            else {
                $sgeasyTool->screen_and_log("$node->{name} : $interface->{name} can be part of" . 
                                            " one of the bridgenet.", "cmquerynet:") if ($debug);
                foreach my $conn (@{$interface->{connected_interface}}) {
                    if (is_already_visited_bridge($conn->{node_name}, $conn->{interface})) {
                        next;
                    }
 
                    # Check if all previous {node ,interface} in list are connected to $conn->{node_name}
                    # Exclude first 2 nodes. We always traverse from lower to higher node_id.
                    if ($node->{node_id} ne 1) {
                        if (!(is_connected_to_each_node_in_bridgenetlist($conn->{node_name}, $conn->{interface}))) {
                            return 1;
                        }
                    }
                    $bridgeNetListObj = BridgeNetList->new();
                    # Add the {node, interface} to the list. 
                    # This will form the path of interface to be checked for bridgenet
                    add_to_bridgenet_list($conn->{node_name}, $conn->{interface}, $bridgeNetListObj);

                    if (@nodes eq 2) { # If only 2 nodes in a cluster
                        $bond = set_bridgenet_id($conn->{node_name},  $conn->{interface}, $bridgeNet_id);
                        if ($bond) {
                            $sgeasyTool->screen_and_log("($node->{name},$interface->{name}) can be" . 
                                                        " BOND interface.","cmquerynet:") if ($debug);
                            set_bridgenet_id($node->{name},  $interface->{name}, $bond);
                        } else {
                            set_bridgenet_id($node->{name},  $interface->{name}, $bridgeNet_id);
                        }
                        @{$bridgeNetObj->{bridgeNet}} = ();
                        next;
                    } else { # No need to investigate last node in cluster.
                        if ($node->{node_id} eq (@nodes - 1)) { # If it is last but one node
                            set_bridgenet_id($conn->{node_name},  $conn->{interface}, $bridgeNet_id);
                            # Remove the {node, interface} entry from list once the bridgenet id is set
                            pop(@{$bridgeNetObj->{bridgeNet}});
                            return 0;
                        } 
                    }
                    
                    if (!get_bridgenets($conn->{node_name}, $conn->{interface})) {
                        # Set bridgenet id
                        if ($node->{node_id} == 1) {
                             $bond = set_bridgenet_id($conn->{node_name},  $conn->{interface}, $bridgeNet_id);
                             if ($bond) {
                                 $sgeasyTool->screen_and_log("($node->{name},$interface->{name}) can be" . 
                                                             " BOND interface.", "cmquerynet:") if ($debug);
                                 set_bridgenet_id($node->{name},  $interface->{name}, $bond);
                             } else {
                                 set_bridgenet_id($node->{name},  $interface->{name}, $bridgeNet_id);
                             }
                             pop(@{$bridgeNetObj->{bridgeNet}});
                             next;
                        } else {
                            set_bridgenet_id($conn->{node_name},  $conn->{interface}, $bridgeNet_id);
                            pop(@{$bridgeNetObj->{bridgeNet}});
                            return 0;
                        }
                    }
                }
                if ($node->{node_id} == 1) {
                    unless ($interface->{bridgeNet}) {
                        $bridgeNet_id--;
                    }
                }
                $traverse++;
            }
        }
        if ($node->{node_id} == 1 && $bond) {
            $bridgeNet_id--;
        }
        last;
    }
}

 #********************************************************************************
 # FUNCTION - add_to_bridgenet_list
 #
 #            Appends each visited {node, interface} to the list.
 #
 #********************************************************************************

sub add_to_bridgenet_list {
    my ($iNode, $iEth, $bridgeNetList) = @_;
    $bridgeNetList->{node_name} = $iNode;
    $bridgeNetList->{interface_name} = $iEth;
    $bridgeNetObj->addBridgeNet($bridgeNetList);
}

 #********************************************************************************
 # FUNCTION - set_bridgenet_id
 #
 #            Sets bridge id for interface.
 # 
 #
 #********************************************************************************

sub set_bridgenet_id {
    my ($iNode, $iEth, $bridgeId) = @_;
    foreach my $node (@{$networkObj->{nodes}}) {
        if ($node->{name} ne $iNode) { next; }
        foreach my $interface (@{$node->{interfaces}}) {
            if ($interface->{name} ne $iEth) { next; }
            if ($interface->{bridgeNet}) { return $interface->{bridgeNet}; } # If bridgeNet is already set.
            if ($interface->{visited} eq 'n') {
                $interface->{visited} = 'y';
                $interface->{bridgeNet} = $bridgeId;
                $sgeasyTool->screen_and_log("Set bridgenet id = $bridgeId for" . 
                                            " $iNode : $iEth.", "cmquerynet:") if ($debug);
                $sgeasyTool->screen_and_log("$node->{name} : $interface->{name} is" . 
                                            " marked visited.", "cmquerynet:") if ($debug);
                return 0;
            }
        }
    }
}

 #*************************************************************************************
 # FUNCTION - is_already_visited_bridge
 #
 #            Checks whether interface $iEth from node $iNode is already visited or not.
 #
 #            RETURNS : 1 if already visited, else 0
 #
 #*************************************************************************************

sub is_already_visited_bridge {
    my ($iNode, $iEth) = @_;
    foreach my $node (@{$networkObj->{nodes}}) {
        if ($node->{name} ne $iNode) { next; }
        foreach my $interface (@{$node->{interfaces}}) {
            if ($interface->{name} ne $iEth) { next; }
            if ($interface->{visited} eq 'y') {
                $sgeasyTool->screen_and_log("$node->{name} : $interface->{name} is already" . 
                                            " visited.", "cmquerynet:") if ($debug);
                return 1;
            } else {
                return 0;
            }
        }
    }
}

 #********************************************************************************
 # FUNCTION - is_bridgenet_interface
 #
 #            Identifies whether interface $iEth from node $iNode has connectivity
 #            to all nodes in a cluster.
 # 
 #            RETURNS : 1 if it is bridgenet interface, else 0
 #
 #********************************************************************************

sub is_bridgenet_interface {
    my ($iNode, $iEth) = @_;
    my $no_of_bridge_nodes;
    my $no_of_cluster_nodes = @nodes;
    my $prev_conn_node;
    foreach my $node (@{$networkObj->{nodes}}) {
        if ($node->{name} ne $iNode) { next; }
        foreach my $interface (@{$node->{interfaces}}) {
            if ($interface->{name} ne $iEth) { next; }
            $no_of_bridge_nodes = 0;
            $prev_conn_node = "";
            # Check if structure ConnectedInterface{node, interface} has entries of all nodes in a cluster.
            # If not then that interface can't be in any bridgenet.
            foreach my $conn (@{$interface->{connected_interface}}) {
                if ($conn->{node_name} eq $prev_conn_node) { next; }  # Skip the same node entry as we already visited
                foreach my $pnode (@nodes) {
                    if ($conn->{node_name} eq $pnode ) {
                        $prev_conn_node = $conn->{node_name};
                        ++$no_of_bridge_nodes;
                        last;
                    } else { next; }
                }
            }
            # If all nodes are reachable then number of nodes in cluster and number of nodes in 
            # ConnectedInterface{node, interface} should be equal.
            if ($no_of_bridge_nodes ne ($no_of_cluster_nodes - $node->{node_id})) {
                return 0;
            } else {
                return 1;
            }
        }
    }
}

 #********************************************************************************
 # FUNCTION - is_connected_to_each_node_in_bridgenetlist
 #
 #            Verifies whether {$iEth, $iNode} has entry in connected_interface DS
 #            of bridgenet each bridgenet list element.
 #
 #            RETURNS : 1 if connected, else 0.
 #
 #********************************************************************************

sub is_connected_to_each_node_in_bridgenetlist {
    my ($iNode, $iEth) = @_;
    my $connected = 0;
    foreach my $list (@{$bridgeNetObj->{bridgeNet}}) {
        # For each {node, interface} entry in the list 
        # check networkObj for {$iNode, $iEth} entry.
        foreach my $node (@{$networkObj->{nodes}}) {
            if ($node->{name} ne $list->{node_name}) { next; }
            foreach my $interface (@{$node->{interfaces}}) {
                if ($interface->{name} ne $list->{interface_name}) { next; }
                foreach my $conn (@{$interface->{connected_interface}}) {
                    if ($conn->{node_name} eq $iNode && $conn->{interface} eq $iEth) {
                        ++$connected;
                    }
                }                 
            }
        }
    }
    # If value of '$connected' is equal to number of objects in the list,
    # then interface $iEth is reachable from all interfaces in list.
    my $bridgeCount = @{$bridgeNetObj->{bridgeNet}};
    if ($connected eq $bridgeCount) 
    { 
        # Connected
        $sgeasyTool->screen_and_log("Interface $iNode:$iEth is connected to all" . 
                                    " members in the list.", "cmquerynet:") if ($debug); 
        return 1;
    } else {
        return 0;   
    }
 
}

 #********************************************************************************
 # FUNCTION - reset_visited_list_entry
 #
 #            Resets visited field, of each interface of each node 
 #            of a cluster, in Network DS to 'n'.
 #
 #********************************************************************************

sub reset_visited_list_entry {
    foreach my $node (@{$networkObj->{nodes}}) {
        foreach my $interface (@{$node->{interfaces}}) {
            $interface->{visited} = 'n';
        }
    }
}

 #********************************************************************************
 # FUNCTION - setup_logging
 #
 #            Setup logging infrastructure.
 #
 #********************************************************************************

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

 #********************************************************************************
 # FUNCTION - fail
 #
 #            To be called on failure.
 #            Displays failure message and exit program.
 #
 #********************************************************************************

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

 #********************************************************************************
 # FUNCTION - print_connected_interfaces
 #
 #            Debug function to print the connected interfaces from each
 #            interface of each node in a cluster.
 # 
 #********************************************************************************

sub print_connected_interfaces {
    print "\n########################################\n";
    foreach my $node (@{$networkObj->{nodes}}) {
        print "NODE : $node->{name}\n\n";
        foreach my $interface (@{$node->{interfaces}}) {
            print "$node->{name} $interface->{name} -> \n";
            foreach my $conn (@{$interface->{connected_interface}}) {
                print "            $conn->{node_name} $conn->{interface}\n";
            }
        }
        print "\n------------------------------------\n";
    }
}

sub validate_no_of_bridgenets {
    my $valid_no_of_bridgenets = 0;
    foreach my $node (@{$networkObj->{nodes}}) {
        foreach my $interface (@{$node->{interfaces}}) {
            if ($interface->{bridgeNet} > $valid_no_of_bridgenets) {
                $valid_no_of_bridgenets = $interface->{bridgeNet};  
            } 
        }
        last;
    }
        return $valid_no_of_bridgenets;
}
