package InstallVerify;
#<copyright>
# ----------------------------------------------------------
# Sun Proprietary/Confidential Code
# Copyright 2001, Sun Microsystems, Inc. All rights reserved.
# ----------------------------------------------------------
#</copyright>

use strict;
use System;
use Util;
use PDM::ConfigFile;
use TO;
use IO::File;
use IO::Select;

use Data::Dumper;

sub new{
  my $self = {
    TYPE=>"specified device",
    PROCESS_HASH=>{},
    REPORT=>{},
    MAX_FAILS=>10,
    PATHS=>[],
    NUMIO=>0,     #set the number of IO's allowed, 0=unlimited
    IOCMD=> System->get_home().
                       "/Diags/bin/dex -v -S2 8g 0 1m -x 128k -r -e 1000",
    ID=>"InstallVerify"};
  $self->{REPORT}->{PROC_DATA}=[]; #used to keep failed test info 
  $self->{REPORT}->{DEV_DATA}; #used to keep failed test info 
  $self->{REPORT}->{PASS_COUNT}=0;
  return bless($self, "InstallVerify");
}

sub canRun{
  my $self = shift;
  my @DEVS = $self->find_devices();
  my @Paths;
  foreach my $vd (@DEVS){
    my @p = $vd->getLogicalPaths();
    push(@Paths, @p);
  }
  if($#Paths<0) {
    #cannot RUN
    return 0;
  }else{
    #can RUN
    return 1;
  }
}

sub RUN{
  my $self = shift;
  my ($numio, $other) = @_;
  local $SIG{INT} = 
    sub {
      $self->Stop();
      Process->done($self->{ID});
      exit;
    };

  Process->start($self->{ID});
  $self->{REPORT}->{start_time}= time;
  $self->setNumio($numio) if(defined($numio));

  my @Devs = $self->find_devices();
  my $process_set = new IO::Select(); # create handle set for reading
  $self->{REPORT}->{DEV} = @Devs;
  Process->write($self->{REPORT}, $self->{ID});
  my @paths;
  foreach my $vd (@Devs){
    my @p = $vd->getLogicalPaths();
    if(@p){
       push(@paths, @p);
       print "Found the following paths for $self->{TYPE} $vd->{info}{BoxName}: \n";
       foreach my $tpath (@p){
         print "$tpath\n";
       }
    }else{
      print "No Paths found for $self->{TYPE} $vd->{info}{BoxName} \n";
    }

  }
  if(!defined(@Devs) || !defined(@paths)) {
    $self->{REPORT}->{end_time}=time;
    my $message="Unable to determine any data paths to $self->{TYPE}s.\n";
    push(@{ $self->{REPORT}->{Error} },
    {
      message=>$message,
      pc=>"Probable Cause:\n".
      "1. Device not attached to specified host.\n".
      "2. The specified devices have not been discovered by the ".
                         "Storage Automated Diagnostic Environment.\n",
      ra=>
      "Reccomended Actions:\n".
      "1. Verify device is connected correctly to the specified host.\n".
      "2. Verify the specified devices have been discovered by the ".
                        "Storage Automated Diagnostic Environment.\n".
      "3. Run Revision Checking on SAN.\n"
      });
    print "\n$message";
    print "Unable to start I/O tests on $self->{TYPE}s.\n";
    Process->write($self->{REPORT}, $self->{ID});
    Process->done($self->{ID});
    return;
  }
  $self->setPaths(\@paths);
  my $pathlist;
  foreach my $path (@{$self->{PATHS}}){
    
    $self->{REPORT}->{DEV_DATA}->{$path}->{PASS_COUNT}=0;
    $self->{REPORT}->{DEV_DATA}->{$path}->{FAIL_COUNT}=0;
    $pathlist .= $path . "\n";
  }
  Process->write($self->{REPORT}, $self->{ID});
  print "\n";
  print "I/O will be run to the following $self->{TYPE}s:\n";
  print "$pathlist\n";

  for(my $numrunning=1; $numrunning < @{ $self->{PATHS}} + 1; $numrunning++){
    my $proc;
    $proc->{start_time}=time;
    $proc->{type}="IO";
    $proc->{path}=Util->trim($self->getPath());
    #verb print "\nI/O is starting to $proc->{path}\n";
    $proc->{FH}=new IO::File;
    #print "CMD = $self->{IOCMD} $proc->{path}\n";
    #print "Start test: $self->{IOCMD} $proc->{path}\n";

    $proc->{pid}=$proc->{FH}->open(
          "$self->{IOCMD} $proc->{path} |")
                                    or die "Couldn't Fork: $!\n";
    $process_set->add($proc->{FH});
    $self->{PROCESS_HASH}->{fileno($proc->{FH})} = $proc;
    last if($numrunning>=$numio && $self->getNumio != 0);
  }
  my $failed_cnt=0;
  MONITOR:while(1){
    my @ready_set = $process_set->can_read;
    if(@ready_set== ()){ sleep 1;next MONITOR;}
    FILEHANDLE:foreach my $FH (@ready_set){
      my $done = 0;
      my $proc=undef;
      my $fileno = fileno($FH);
      $proc = $self->{PROCESS_HASH}->{$fileno};
      if(!defined($proc)){sleep 1;next FILEHANDLE;}
      my $newline = $proc->{FH}->getline();
      if($newline =~ /Pass\s\d+,\sErrors\s\d+/){
        my($pass, $fail, $crap) = split(/,/, $newline);
        my @f = split(/Errors\s/, $fail);
        if(@f[$#f] >0){
          $proc->{returnstatus} = 1;
        }else{
          $proc->{returnstatus} = 0;
        }
        $done = 1;
      }
        push(@{$proc->{io_output}}, $newline);
      if($done) {
        $proc->{FH}->close();# or print "couldn't close $!\n";
        if ($proc->{returnstatus}){#IOrun failed
                                   #print failure and check to see if 
                                   #failures on specific path exceeds 
                                   #threshhold, if so stop testing it
          $proc->{result}="FAIL";
	  $self->{REPORT}->{DEV_DATA}->{$proc->{path}}->{FAIL_COUNT}++;
	  print "\nFAIL: I/O Test FAILED to $proc->{path}\n";
	  print "Tests to $proc->{path} have failed ". 
	         $self->{REPORT}->{DEV_DATA}->{$proc->{path}}->{FAIL_COUNT} 
		 ." times\nFailed test output is below:\n";
          print "@{$proc->{io_output}}\n";
          if($self->{REPORT}->{DEV_DATA}->{$proc->{path}}->{FAIL_COUNT}
	                                              >=$self->{MAX_FAILS}){
	    $self->removePath($proc->{path});
	  print "$proc->{path} has failed $self->{MAX_FAILS} times, and will ".
	        "be removed from the test queue.\n";
	  }
          $failed_cnt++;
        }else{
          #print "I/O test passed to $proc->{path}\n\n";
          $proc->{result}="PASS";
          delete $proc->{io_output};
        }
        $process_set->remove($proc->{FH});
        $proc->{end_time}=time;                         # Keep some notes
	if($proc->{result} eq "PASS"){
	  $self->{REPORT}->{DEV_DATA}->{$proc->{path}}->{PASS_COUNT}++;
	}else{
          push(@{$self->{REPORT}->{DEV_DATA}->{$proc->{path}}->{FAILURE_DATA}}
	        , $proc);
	}
        Process->write($self->{REPORT}, $self->{ID});
        delete($self->{PROCESS_HASH}->{$fileno});
        if($self->getPathcount()==0){
          print "$0 has detected too many errors to continue IO testing ".
                "$self->{TYPE}(s).\n";
          $self->Stop();
	  $self->Diagnose();
          Process->done($self->{ID});
	  return;
        }
        my $newproc;
        $newproc->{path}=Util->trim($self->getPath());
        #verb print "\nI/O is starting to $newproc->{path}\n";
        $newproc->{type}="IO";
        $newproc->{FH}=new IO::File;
	#print "Start test: $self->{IOCMD} $newproc->{path}\n";
        $newproc->{pid}=$newproc->{FH}->open(
         "$self->{IOCMD} $newproc->{path} |") or die "Couldn't Fork: $!\n";
        $process_set->add($newproc->{FH});
        $self->{PROCESS_HASH}->{fileno($newproc->{FH})} = $newproc;
      }
    }
  }
  Process->done($self->{ID});
}

sub Stop{
  my $self = shift;
  print "\nPlease wait while I/O is stopped to $self->{TYPE}s.\n";
  my($kill)   =  System->find_command("kill");
  foreach my $p (keys %{ $self->{PROCESS_HASH} }){
    my $proc = $self->{PROCESS_HASH}->{$p};
    #my @newlines= $proc->{FH}->getlines();
    if($proc->{FH}->eof()){
      $proc->{FH}->close();
    }else{
      my $killed = 0;
      #$killed = kill 9 => (-$proc->{pid});
      #$killed = kill 9 => ($proc->{pid});
      system("$kill $proc->{pid}") or sleep 1; system("$kill -9 $proc->{pid}");
      $proc->{FH}->close();
    }
  }
  print "\nI/O is stopped to $self->{TYPE}s.\n";
  $self->{REPORT}->{end_time}=time;
  Process->write($self->{REPORT}, $self->{ID});
}

sub isRunning{
  my $self = shift;
  my $status = Process->status(undef, $self->{ID});
  if($status =~ /Running/){
    return(1);
  } else {
    return(0);
  }
}

sub setNumio{    #set the number of IO's allowed, 0=unlimited
  my ($self, $numio, $other) = @_;
  $self->{NUMIO}=$numio;
}

sub getNumio{
  my $self = shift;
  return ($self->{NUMIO});
}

sub removePath{
  my ($self, $path) = @_;
  for(my $i=0; $i<@{$self->{PATHS}}; $i++){
    if($path eq @{$self->{PATHS}}[$i]){
      splice(@{$self->{PATHS}}, $i, 1);
      last;
    }
  }
}

sub setPaths{
  my ($self, $paths) = @_;
  $self->{PATHS}=$paths;
}

sub getPath{
  my $self = shift;
  my $path = pop @{ $self->{PATHS} };
  unshift (@{ $self->{PATHS} }, $path);
  return $path;
}

sub getPathcount{
  my $self = shift;
  my $pathcount = scalar(@{$self->{PATHS}}); 
  return $pathcount;
}

sub report{
  my ($self, $REPORT, $error_only) = @_;
  my $cnt=0;
  my $failed_devs;
  my $passed_devs;
  my $failedruns=0;
  my $unfinishedruns=0;
  my $result;
  my $probable_cause;
  my $recomended_action;

  my $title =$self->getType()." StressCheck\n";
  $title .= "Start Time:\t ".localtime($REPORT->{start_time})."\n";
  $title .= "Stop Time:\t ".localtime($REPORT->{end_time})."\n";
  if (defined ($REPORT->{Error})){
    foreach my $error (@{ $REPORT->{Error} }){
      $result .= $error->{message};
      $result .= $error->{pc};
      $result .= $error->{ra}."\n";
    }
  }
  foreach my $dev (keys(%{ $REPORT->{DEV_DATA} })){
    if (exists($REPORT->{DEV_DATA}->{$dev}->{FAILURE_DATA})) {
      $failedruns++;
      $result.="FAIL: An error was detected while testing $dev!\n".
               "      Details are listed below:\n";
      foreach my $run (@{ $REPORT->{DEV_DATA}->{$dev}->{FAILURE_DATA} }){
        $result.="FAILED TEST OUTPUT:\n";
        if($run->{type}eq"IO"){
          if ($run->{result} eq "FAIL" ){
	    foreach my $line (@{ $run->{io_output} }){
              $result.="\t$line";
	    }
          }
        }
      }
      $result.="\n";
    }elsif($REPORT->{DEV_DATA}->{$dev}->{PASS_COUNT} == 1){
      $result.= "PASS: 1 I/O test was successfully run against $dev.\n";
    }elsif($REPORT->{DEV_DATA}->{$dev}->{PASS_COUNT} > 0){
      $result.="PASS: ".$REPORT->{DEV_DATA}->{$dev}->{PASS_COUNT}.
        " I/O tests were successfully ran against $dev.\n";
    }else{
      $result.= "No I/O tests were completed against $dev,\n".
      "let stresstest run longer to see results.\n";
    }
  }

  if($failedruns >0){
    $probable_cause .="Failed ".$self->getType()."s not properly connected to this host.\n";
    $probable_cause .="Host not properly configured for ".$self->getType() ."s. \n";
    $probable_cause .=$self->getType() ."s are not labled. \n";
    $recomended_action .="Run device specific Diagnostic to isolate error further.\n";
  }

  if($probable_cause){
    $probable_cause = "\nProbable Cause:\n".$probable_cause;
    $result .= $probable_cause; 
  }

  if($recomended_action){
    $recomended_action = "Reccomended Actions:\n".$recomended_action;
    $result .= $recomended_action; 
  }
  $result.="*******************************************************\n";

  return "$title\n$result\n";
}

#Default Diagnose method.
#implement automated diagnostic in specialized device class
sub Diagnose{
  my $self = shift;
#  print "No device specific diagnostic automated for $self->{TYPE}.\n";
#  print "Run $self->{TYPE} specific diagnostic manually for further diagnosis.\n";
}


1;
