#!/opt/SUNWstade/bin/perl -I/opt/SUNWstade/lib 
use System;
use Util;
use strict;
use Getopt::Std;
use Data::Dumper;
use Modules;
use Scheduler;
use Linktest;
use TO;
use System;
use PDM::ConfigFile;
use Report;

use vars qw(%opts $DEBUG %PROMPTS $single_isl $Config );

#
# all possible prompts
#
# insert - used for loopback only
# remove - remove current FRU/loopback 
# replace - replace the current FRU with a NEW FRU
# restore - restore the original FRU
# ra - Recommend Action

System->set_home("/opt/SUNWstade");
System->set_rasport("7654");

$Config  = PDM::ConfigFile->read();
my $renv = $Config->renv();
System->set_config($Config);
System->set_renv($renv);

print "running on $renv->{hostname} \n";

if (!getopts("dI:p:T:svW:a:b:h", \%opts)) {
  die("Abort: $Getopt::Std::ERROR \n");
}

$DEBUG= $opts{d};  # debug off by default
System->set_debug($DEBUG);

if ($opts{h}) {
  usage();
  exit( $Linktest::ERROR );
}
$Linktest::Qfile = $opts{W};
$Linktest::GUI   = 1 if ($Linktest::Qfile);

my $single_isl            = $opts{I};
my $option_pattern        = $opts{p};

if (!$opts{a} || !$opts{b}) {
  print "Error: need 2 nodes to run.\n\n";
  usage();
  exit( $Linktest::ERROR );
}

# default timeout for tests: 30 minutes.
# long enough for almost any test.
# a switchtest of "all" takes approximately
# 275 minutes to run. pad it out to 360 minutes

my $test_timeout = $opts{T} eq "all" ?  360 * 60 : 60*30;

my $linkEnv = { test_timeout  => $test_timeout,
                     verbose  => $opts{v},
                       debug  => $DEBUG,
                    verbose2  => $DEBUG || $opts{v},
                test_timeout  => $test_timeout ,
                  single_isl  =>  $single_isl,
               option_pattern => $opts{p},
          option_pattern_type => $opts{T}
               };

System->set_linkEnv($linkEnv);

my $MODS = Modules->load("Linktest");  # load all Linktest modules dynamically

my $node_as = $opts{a};
my $node_bs = $opts{b};
$node_bs =~ s/ //g;
$node_as =~ s/ //g;

my ($node_a, $node_b) = Linktest->parse($node_as, $node_bs);
my( $nodeA , $nodeB ) = &create_nodes($node_a, $node_b);

if ( $DEBUG == 1) {
  print "nodeA:\n" . $nodeA->dumper();
  print "nodeB:\n" . $nodeB->dumper();
}

if ($Linktest::GUI) {
  select(STDOUT); $|=1; 
}

###########################################################
# Isolation code makes assumptions about node a  and node b.
#
# Swap the nodes to match assumptions when appropriate.
#
# Test both end nodes of a link segment when possible.
#
# If a end-node (I/O device) is detected and we are NOT direct connect
# do not test it. The path used for I/O in those cases is NOT determinstic.
#
# If devices can NOT be tested due to insufficient test capablities, report
# it and exit.
#
# 0 = can't execute linktest
# 1 = test 1st node
# 2 = test both nodes
##########################################################

  my ($node_swap, $single_enet_path, $answer, $port_type, $switch_type);

  my $pt1 =  $nodeA->portTest($nodeB);
  my $pt2 =  $nodeB->portTest($nodeA);



  # isolation code makes assumptions about node a and node b
  # swap the nodes to match assumptions when appropriate
  
  if ($nodeA->class() =~ /switch/ && $nodeB->class() =~ /switch/) {

    if ($nodeA->{fcaddr} || $nodeA->class() eq "switch.vicom" ) {
       Linktest->swap(\$nodeA, \$nodeB);
       my $tpt = $pt1;
       $pt1 = $pt2;
       $pt2 = $tpt;
    }
    if ( $single_isl == 1 ) {
      $nodeA->prompt( 'detected_single_isl');
      $nodeA->prompt( 'ra_detected_single_isl');
    }

  } elsif (index("SW,HB", $nodeA->type2()) >= 0 && index("SW,HB", $nodeB->type2()) >= 0) {
    #  HBA => SWITCH

    if ($nodeB->type2() eq "HB") {
       Linktest->swap(\$nodeA, \$nodeB);
       my $tpt = $pt1;
       $pt1 = $pt2;
       $pt2 = $tpt;
    }

  } elsif (index("SW,ST", $nodeA->type2()) >= 0 && index("SW,ST", $nodeB->type2()) >= 0) {
    # switch => storage
    if ($nodeB->type2() eq "SW") {
       Linktest->swap(\$nodeA, \$nodeB);
       my $tpt = $pt1;
       $pt1 = $pt2;
       $pt2 = $tpt;
    }

  } elsif (index("HB,ST", $nodeA->type2()) >= 0 && index("HB,ST", $nodeB->type2()) >= 0) {
    # switch => storage
    if ($nodeB->type2() eq "HB") {
       Linktest->swap(\$nodeA, \$nodeB);
       my $tpt = $pt1;
       $pt1 = $pt2;
       $pt2 = $tpt;
    }
    
  } else {
    Linktest->device_unsupported_msg();
    exit( $Linktest::ERROR );
  }

  #Linktest->debug_header( $node_a, $node_b, "main" );
  my $mode = "PORT";

  if ( ($pt1 == 0) && ( $pt2 == 0)) {
    # There is no device specific isolation capabilites
    # Check for IO isolation capabilites
    $mode    = "IO";

    $pt1 =  $nodeA->IOtest($nodeB);
    $pt2 =  $nodeB->IOtest($nodeA);

    if(($pt1 == 0) && ($pt2 == 0)){

       Linktest->untestable_link( $nodeA->{type}, $nodeB->{type} );
    }
  }

  if ($nodeA->needPassword()) {
    if (!defined($nodeA->password())) {
        $nodeA->prompt( 'no_password');
        $nodeA->prompt( 'ra_password');
        Linktest->ra_exit();
    }
  }
  
  if ($nodeB->needPassword()) {
    if (!defined($nodeB->password())) {
        $nodeB->prompt( 'no_password');
        $nodeB->prompt( 'ra_password');
        Linktest->ra_exit();
    }
  }


  my ($status, $out);

###############################
# START LINKTEST
###############################
  
  Linktest->start_linktest_msg( $nodeA, $nodeB );
  $DB::single = 1;
  # Check to see if 1st node is testable
  if ( $pt1 == 1 ) {
    $out = $nodeA->start_diagnostic("E" );
    if ( $out->{rc} != 0 ) {
      $status = link_isolation( $nodeA, $nodeB, "a", $mode );
      if ( ( $status == $Linktest::FOUND_SUSPECT_FRU ) ||
           ( $status == $Linktest::MULTIPLE_FRUS_FOUND ) ) {
        Linktest->stop_linktest_msg( $nodeA, $nodeB );
        exit( $Linktest::FOUND_FRU );
      }
      Linktest->failed_linktest_msg( $nodeA, $nodeB );
      exit( $Linktest::ERROR ); # Always exit if isolation failed
    }
  }
   
  # Check to see if 2nd node is testable
  if ( $pt2 == 1 ) {
    $out = $nodeB->start_diagnostic();
  
    if ( $out->{rc} != 0 ) {
      $status = link_isolation( $nodeA, $nodeB, "b", $mode );
      if ( $status == $Linktest::FOUND_SUSPECT_FRU ) {
        Linktest->stop_linktest_msg( $nodeA, $nodeB );
        exit( $Linktest::FOUND_FRU );
      }
      Linktest->failed_linktest_msg( $nodeA, $nodeB );
      exit( $Linktest::ERROR ); # Always exit if isolation failed
    }
  } 
  
  Linktest->stop_linktest_msg( $nodeA, $nodeB );
  exit( $Linktest::COMPLETED );


# CREATE NODES

sub create_nodes {
  my($node_a, $node_b) = @_;

  # READ TOPOLOGY NODES
  my $to       = TO->readExistingTopo("MERGE-MASTER") || TO->readExistingTopo();

  my $keya = $node_a->{key};
  $keya = "$node_a->{type}:$keya" if (index($keya, ":") < 0);
  my $to_nodea = $to->nodeByName($keya);

  my $keyb = $node_b->{key};
  $keyb = "$node_b->{type}:$keyb" if (index($keyb, ":") < 0);
  my $to_nodeb = $to->nodeByName($keyb);

  if (!$to_nodea) {
    print "Error: Cannot find nodeA in topology.\n";
    print "Ensure nodeA is a monitored device and that the topology has been updated since this node has been added.\n";
    exit(1);
  }
  if (!$to_nodeb) {
    print "Error: Cannot find nodeB in topology.\n";
    print "Ensure nodeB is a monitored device and that the topology has been updated since this node has been added.\n";

    exit(1);
  }

  # read CONFIG NODES
  my $config_nodea = &deviceByKey($Config, $node_a );
  my $config_nodeb = &deviceByKey($Config, $node_b );

  my $nodeA = Linktest::Node->create($node_a, $config_nodea, $to_nodea);
  my $nodeB = Linktest::Node->create($node_b, $config_nodeb, $to_nodeb);
  return ($nodeA, $nodeB);
}

sub deviceByKey {
  my($Config, $node) = @_;
  if ($node->{type} eq "hba") {
     my($type, $name) = split(/\:/, $node->{key});
     return {key => $node->{key}, type => $type, 
             name => $name, ip => $node->{ip}, class => "host" };
  } else {
     return $Config->deviceByKey( $node->{key} );
  }
}

################################
#
# link_isolation is entered if and only if one of the 2 node's functional
# test fail in main (linktest).
#
# dispatches correct high level isolation algorithm based on 2 node types
# only one side of the link is isolated, if NTF set the failing node
# to the far side (node b) and return
#

sub link_isolation {
  my ( $nodeA, $nodeB, $failed_node, $mode ) = @_;
  my $status;

  Linktest->debug_header( $nodeA, $nodeB, "link_isolation" );

  if ($nodeA->type2() eq "SW" && $nodeB->type2() eq "SW") {
    $status = port2port_link_isolation( $nodeA, $nodeB, $failed_node );

  } elsif ($nodeA->type2() eq "HB" && $nodeB->type2() eq "SW") {
    $status = hba2port_link_isolation( $nodeA, $nodeB, $failed_node );

  } elsif ($nodeA->type2() eq "SW" && $nodeB->type2() eq "ST") {
    $status = port2device_link_isolation( $nodeA, $nodeB, $failed_node, $mode );

  } elsif ($nodeA->type2() eq "HB" && $nodeB->type2() eq "ST") {
    $status = hba2device_link_isolation( $nodeA, $nodeB, $failed_node, $mode );

  } else {
    Linktest->device_unsupported_msg();
    exit( $Linktest::ERROR );
  }
}

#######################
# SWITCHES -> SWITCH
#######################
# isolate failing FRU in any 2 ISL (Inter Switch Link) connection
# isolate both sides of the ISL at this level of dispatch
#

sub port2port_link_isolation {
  my ( $nodeA, $nodeB, $failed_node ) = @_;
  my ($answer,$status);

  Linktest->debug_header( $nodeA, $nodeB, "port2port_isolation" );

  my $dev_a = $nodeA->config();
  my $dev_b = $nodeB->config();

  if ( $dev_a->{ip} eq $dev_b->{ip} ) {
    if ( $single_isl == 1 ) {
      $status = single_isl_port_node_isolation( $nodeA, $nodeB, $failed_node );
      return $status;
    }
  }
  if ( $failed_node eq "a" ) {
    $status = $nodeA->port_node_isolation($nodeB, $failed_node );
    if ( $status == $Linktest::FOUND_SUSPECT_FRU ) {
      return $status;
    } elsif ( $status == $Linktest::SUCCESS ) {
      # port tested OK; try device
      # could be a cable or interface on other device
      $failed_node = "b";
    } elsif ( $status == $Linktest::PROBLEM_UNDETECTED ) {
      # could be a cable or interface on device
      $failed_node = "b";
    } elsif ( $status == $Linktest::MULTIPLE_FRUS_FOUND ) {
      return $status;
    }
  }

  if ( $failed_node eq "b" ) {
    $status = $nodeB->port_node_isolation($nodeA, $failed_node );
    if ( $status == $Linktest::PROBLEM_UNDETECTED ) {
      # Device tested OK; unable to isolate problem 
      return $status;
    } elsif ( $status == $Linktest::SUCCESS ) {
      # Device tested OK; should no get here 
      return $status;
    } elsif ( $status == $Linktest::FOUND_SUSPECT_FRU ) {
      return $status;
    }
  }

}

#######################
# HBA -> SWITCH
#######################
#
# isolate failing FRU in any HBA (Host Bus Adapter) port 
# to switch connection
# node_a MUST be the HBA port
#

sub hba2port_link_isolation {
  my ( $node_a, $node_b, $failed_node ) = @_;

  Linktest->debug_header( $node_a, $node_b, "hba2port_link_isolation" );

  my $answer;
  my $status;

  if ( $failed_node eq "a" ) {
    $status = $node_a->node_isolation( $node_b, $failed_node );
    if ( $status == $Linktest::SUCCESS ) {
      # HBA tested OK; try device
      # could be a cable or interface on port
      $failed_node = "b";
    } elsif ( $status == $Linktest::PROBLEM_UNDETECTED ) {
      # could be a cable or interface on device
      $failed_node = "b";
    } elsif ( $status == $Linktest::FOUND_SUSPECT_FRU ) {
      return $status;
    }
  }

  if ( $failed_node eq "b" ) {
    $status = $node_b->port_node_isolation($node_a, $failed_node );
    if ( $status == $Linktest::PROBLEM_UNDETECTED ) {
      # Device tested OK; unable to isolate problem 
      return $status;
    } elsif ( $status == $Linktest::SUCCESS ) {
      # Device tested OK; should no get here 
      return $status;
    } elsif ( $status == $Linktest::FOUND_SUSPECT_FRU ) {
      return $status;
    }
  }

}



#######################
# SWITCH -> STORAGE
#######################
#
# isolate failing FRU in any FC switch/vicom port 
# to device connection
# node_a MUST be the switch port
#

sub port2device_link_isolation
{
  my ( $node_a, $node_b, $failed_node, $mode ) = @_;

  Linktest->debug_header( $node_a, $node_b, "port2device_link_isolation" );

  my $answer;
  my $status;

  if ( $failed_node eq "a" ) {
    
    $status = $node_a->port_node_isolation( $node_b, $failed_node );
    if ( $status == $Linktest::SUCCESS ) {
      # port tested OK; try device
      # could be a cable or interface on device
      $failed_node = "b";
    } elsif ( $status == $Linktest::PROBLEM_UNDETECTED ) {
      # could be a cable or interface on device
      $failed_node = "b";
    } elsif ( $status == $Linktest::FOUND_SUSPECT_FRU ) {
      return $status;
    }
  }

  # problem was NOT dectected on the port side, could be cable
  # or FC interconnects (or i/o device
  # use the port test to isolate FC interconnects

  if ( $failed_node eq "b" ) {

    $status = device_interconnect_isolation( $node_a, $node_b, $failed_node, $mode  );
    if ( $status == $Linktest::SUCCESS ) {
      # port tested OK; try device
      $failed_node = "b";
    } elsif ( $status == $Linktest::PROBLEM_UNDETECTED ) {
      $failed_node = "b";
    } elsif ( $status == $Linktest::FOUND_SUSPECT_FRU ) {
      return $status;
    }
  }


}

#########################
#  HBA -> STORAGE
# isolate failing FRU in any HBA port to device connection, 
# node_a must be the HBA port
#########################

sub hba2device_link_isolation
{
  my ( $node_a, $node_b, $failed_node ) = @_;

  Linktest->debug_header( $node_a, $node_b, "hba2device_link_isolation" );

  my $status;
  my $answer;

  # daktari (V880 internal disks) have no HOT swap FRUs
  # if daktari ever goes to glass instead of copper we could isolate
  # however if they do they will most likely neglect to tell us
  if ( $node_b->{type} eq "dakdisk" ) {
    Linktest->daktari_multiple_frus_msg();
    return $Linktest::PROBLEM_UNDETECTED;
  }

  if ( $failed_node eq "a" ) {
    $status = $node_a->node_isolation( $node_b, $failed_node );
    if ( $status == $Linktest::SUCCESS ) {
      # HBA tested OK; try device
      # could be a cable or interface on device
      $failed_node = "b";
    } elsif ( $status == $Linktest::PROBLEM_UNDETECTED ) {
      # could be a cable or interface on device
      $failed_node = "b";
    } elsif ( $status == $Linktest::FOUND_SUSPECT_FRU ) {
      return $status;
    }
  }

  if ( $failed_node eq "b" ) {
    $status = device_interconnect_isolation( $node_a, $node_b, $failed_node, $mode );
    if ( $status == $Linktest::PROBLEM_UNDETECTED ) {
      # Device tested OK; unable to isolate problem 
      return $status;
    } elsif ( $status == $Linktest::SUCCESS ) {
      # Device tested OK; should no get here 
      return $status;
    } elsif ( $status == $Linktest::FOUND_SUSPECT_FRU ) {
      return $status;
    }
  }
}


# isolate FRUs in a port to device configuration utilizing the
# available port diagnostic

sub device_interconnect_isolation {
  my ( $node_a, $node_b, $failed_node, $mode ) = @_;
  my ($answer, $status, $out);

  Linktest->debug_header( $node_a, $node_b, "device_interconnect_isolation" );

  
  if ($node_b->can('node_isolation')) {
    $status = $node_b->node_isolation($node_a, $failed_node, $mode);
  } else {
    device_unsupport_msg();
  }
  return $status;
}



