#!/usr/local/bin/perl
# executeDP.pl
# 
# Copyright (c) 2006, 2007, Oracle. All rights reserved.  
#

use English;
use File::Basename;
use File::Copy;
use File::Spec;
use Cwd;


my $extend="false";
# ---------------------------------------------------------
# The value can be any of the following:
#    true =  Delete the instance data files created.
#    false = Do not delete the instance data files created.
$deleteInstanceData="true";

# This is used when the above variable is set "false"
# The dir name or path specified here will be created 
# if it doesn't exist.
# Creates a directory -instance_data  in the  path specified  or if null , will create in the local directory
$instanceLoc="";
# ---------------------------------------------------------

# ----------------------------------------------
# The/ value should be false in actual production
$debug = "false";
# ----------------------------------------------

# -----------------------------------------------------
# constant to define the duration of sleep (in seconds)
# before checking DP status again while monitoring
# -----------------------------------------------------
$sleepConstant=10;

# -----------------------------------------
# constant to define the tolerable time out 
# for DP execution (in seconds)
# -----------------------------------------
$timeOutTime=4*60*60;

# ----------------------------------------------
# constant to define the time out count. 
# this will be equal to total tolerable time out 
# divided by the duration of sleep
# ----------------------------------------------
$timeOutCount=$timeOutTime/$sleepConstant;

#writeLog("Number of arguments passed : " . $#ARGV);
if($#ARGV < 5)
{
    print "\nThis is a script that can be used to run deployment procedures using command line interface.The following arguments need to be provided for execution. The command line interface for patching deployment procedures now supports multiple patches as well\n\n";    
    print "Usage: $as \n\t -t <runtime data template file name> \n";
    print "\t -p <properties file name> \n";
    print "\t -g <dp guid> \n";
    print "\t [-n <name of the procedure instance> (optional)] \n";
    print "\t [-s <schedule> in the format yyyy/MM/dd HH:mm (optional). If not specified, the procedure will be executed immediately.]\n";
    print "\t [-z <time zone id> (optional). If not specified, defaults to the OMS server timezone.] \n";
    print "\t [-d <emcli directory path>, mandatory if emcli executable is not in current directory.]\n";
    print "\t [-no_wait (optional). If specified, the procedure will be submitted for execution and returns.]\n";
    print "\t[ Advanced Options:.]\n";
    print "\t[Accessible by editing the executeDP.pl file variables :]\n";
    print "\t[debug - To turn on/off the debug . (Default value is 'false' ,turned off)]\n";
    print "\t[instanceLoc - To store the instance xml of the execution.(Default created directory 'instance_data' locally ,if null or not specified)]\n";
    print "\t[instance_data - To store/delete the instance xml of the execution]\n";
    print "\nExample:\n";
    print "perl executeDP.pl -t crsasmrac_gold_prov_template.xml -p Properties.txt -g 1F8178444594E67CE040578C8A0309BA -n ABZCorpDeploymentRun_12345 -t 2007/02/03 10:00 -z America/New_York  -d /oracle/orahome/ \n\n";
    exit 1;
}

$templatePath = "";
$propfilePath = "";
$dpguid = "";
$schedule = "";
$timezone = "";
$srcPath = "";
$deplInstName = "";
$donotWait = 0;

parseCommandLineParams();

chomp($templatePath);
chomp($propfilePath);
chomp($dpguid);
chomp($schedule);
chomp($timezone);
chomp($srcPath);
chomp($deplInstName);

#perl script aliases
$scriptsPath = &File::Basename::dirname($0);
$initialize_racprov = File::Spec->catfile($scriptsPath, "initializeRacProv.pl");
$initialize_delnode = File::Spec->catfile($scriptsPath, "initializeDelNode.pl");
$initialize_extcrs = File::Spec->catfile($scriptsPath, "initializeExtCluster.pl");
$generate_template = File::Spec->catfile($scriptsPath, "generateTemplate.pl");
$replace_runtime = File::Spec->catfile($scriptsPath, "replaceRuntimeValues.pl");
$replace_runtime_patching = File::Spec->catfile($scriptsPath, "replaceRuntimeValues_patching.pl");

if($srcPath eq "")
{
	if(!( -f "emcli" && -x "emcli"))
	{
		print "EMCLI executable is not found in the current directory. \n";
		exit 1;
	}
}

#check if replaceRuntimeValues.pl is there 
if(!( -f $replace_runtime))
{
	print "replaceRuntimeValues.pl not found in \"$scriptsPath\". \n";
	exit 1;
}

#check if generateTemplate.pl is there
if(!( -f $generate_template))
{
	print "generateTemplate.pl not found in \"$scriptsPath\". \n";
	exit 1;
}

#check if replaceRuntimeValues_patching.pl is there
if(!( -f $replace_runtime_patching))
{
	print "replaceRuntimeValues_patching.pl not found in \"$scriptsPath\". \n";
	exit 1;
}


#get the file name in case it is  
#qualified by directory name etc.
if( -f $templatePath)
{
	$template = basename($templatePath);
}
else
{
	print "File $templatePath not found. \n";
	exit 1;
}

if( -f $propfilePath)
{
	$propfile = basename($propfilePath);
}
else
{
	print "File $propfilePath not found. \n";
	exit 1;	
}

#validate schedule if provided
if($schedule ne "")
{
	validateSchedule($schedule);
}

print "Inside execute DP script. \n";
print "Template Name : $template \n";
print "Property File : $propfile \n";
print "DP GUId       : $dpguid \n";
if($schedule ne "")
{
	print "Schedule      : $schedule \n";
}
if($timezone ne "")
{
	print "Timezone      : $timezone \n";
}
if($srcPath ne "")
{
	print "EMCLI Path    : $srcPath \n";
}
print "--------------------------------------------------------\n";
print "Script execution started ... \n";

#--------------------------
# Compute working directory
#--------------------------
$PWD = cwd();
chomp($PWD);
$instanceDataFolder = $PWD;
$instanceDataDirName = "instance_data";
if($deleteInstanceData eq "false")
{
    if($instanceLoc ne "" )
    {
        $instanceDataFolder = Cwd::abs_path($instanceLoc);
    }
    $instanceDataFolder = File::Spec->catfile ( $instanceDataFolder, $instanceDataDirName ) ;
    if ( ! -e $instanceDataFolder ) {
        if ( $OSNAME =~ m#Win32# )
        {
           my $system_command = "cmd.exe \/C mkdir $instanceDataFolder";
           qx/$system_command/;
        }
        else
        {
           my $system_command = "mkdir -p $instanceDataFolder";
           qx/$system_command/;
        }

        if ( ! -e $instanceDataFolder) {
            print "Problem with creating directory structure: " .
                              "$instanceDataFolder\n";
            exit 1;
        }
    }
}
writeLog("The instance data files created will be put in " . $instanceDataFolder);

# Generate the timestamp.
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst )
                                                       = localtime ( time() );
$year = $year + 1900;
# There is no month "zero" to people.
$mon++;
if ( $mon < 10 ) { $mon = "0" . $mon; }
if ( $mday < 10 ) { $mday = "0" . $mday; }

if ( $hour < 10 ) { $hour = "0" . $hour; }
if ( $min < 10 )  { $min = "0" . $min; }
if ( $sec < 10 )  { $sec = "0" . $sec; }

my $time_tag = "$mon" . "-" . "$mday" . "-" . "$year" . "_" .
                   "$hour" . "-" . "$min" . "-" . "$sec";

#generated properties file name
my ($pname, $pextn) = split(/\./, $propfile);
$newProps = File::Spec->catfile ($instanceDataFolder , $pname . "_new_$time_tag." . $pextn);

#new runtime data template
my ($tname, $textn) = split(/\./, $template);
$newTemplate = File::Spec->catfile ($instanceDataFolder , $tname . "_new_$time_tag." . $textn);

#instantiated runtime data xml name
$instantiatedTemplate = File::Spec->catfile ($instanceDataFolder , $tname . "_instance_$time_tag." . $textn);

#------------------------------
# Initialize DP specific values
#------------------------------
if($srcPath ne "")
{
	$cmd="$srcPath/emcli get_procedures";
}
else
{
	$cmd="./emcli get_procedures";
}
print "EMCLI CMD = $cmd\n";
@procList = `$cmd`;
@procInfo = grep /$dpguid/, @procList;
foreach $line (@procInfo)
{
	@tmp = split(/,/, $line);
	$dpType = $tmp[1];
	chomp($dpType);
	writeLog("The DP Type is: ".$dpType);
	$_= $tmp[2];
	writeLog("The DP Name is: ".$_);
	last;	
}#end foreach

    print "dpType = $dpType\n";
$dpType = trim($dpType);

#perform initialization for RACPROV procedures only
if($dpType eq "RACPROV")
{
	#find out from the name the correct initializer to invoke
	if(/Oracle Clusterware \/ RAC Provisioning/)
	{
		writeLog("Going to invoke initializer for RAC Prov ...");
		if(!( -f $initialize_racprov))
		{
			print "initializeRacProv.pl not found in \"$scriptsPath\".\n";
			exit 1;
		}
		$cmd="perl $initialize_racprov $propfilePath $newProps";
		system("$cmd");
		writeLog("$cmd");
		checkStatus("Step Name: Generate properties file");
	}
	elsif(/Extend Cluster/)
	{
	    print "Going in Extend Cluster\n";
		writeLog("Going to invoke initializer for Extend Cluster ...");
		if(!( -f $initialize_extcrs))
		{
			print "initializeExtCluster.pl not found in \"$scriptsPath\". \n";
			exit 1;
		}
		$cmd="perl $initialize_extcrs $propfilePath $newProps";
                $extend="true";   
		#system("$cmd");
		writeLog("$cmd");
		#checkStatus("Step Name: Generate properties file");
	}
	elsif(/Delete\/Scale down/)
	{
		writeLog("Going to invoke initializer for Delete Node ...");
		if(!( -f $initialize_delnode))
		{
			print "initializeDelNode.pl not found in \"$scriptsPath\". \n";
			exit 1;
		}
		$cmd="perl $initialize_delnode $propfilePath $newProps";
		system("$cmd");
		writeLog("$cmd");
		checkStatus("Step Name: Generate properties file");
	}
	else
	{
		print "Could not establish the DP which is being executed.\n";
		print "Please ensure that the DP Name has either one of the following three keywords/phrases\n\n";
		print "Keywords/Phrases                        DP Functionality\n";
		print "----------------------------------------------------------------\n";
		print "Oracle Clusterware \/ RAC Provisioning   Initial RAC Provisioning\n";
		print "Extend Cluster                           Extend Cluster\n";
		print "Delete\/Scale down                       Delete Node\n";
		exit(1);
	}
}
elsif ($dpType eq "PatchOracleSoftware")
{
	#for other procedures use the properties file passed as parameter
	#writeLog("I am in the  elseif block of patching");
	copy($propfilePath, $newProps) or die "Properties File copy failed: $!";

	print "Replace runtime values for patching precedures ... \n";
	$cmd="perl $replace_runtime_patching $templatePath $instantiatedTemplate $newProps";
	writeLog("$cmd");
	system($cmd);
	checkStatus("Step Name: Instantiate Runtime Data");

} 
else
{
	writeLog("I am in the default else block");
	writeLog(":".$dpType.":");
	#for other procedures use the properties file passed as parameter
	copy($propfilePath, $newProps) or die "Properties File copy failed: $!";

	print "Replace runtime values for patching precedures ... \n";
        #$cmd="perl $replace_runtime $newTemplate $instantiatedTemplate $newProps";
        $cmd="perl $replace_runtime $templatePath $instantiatedTemplate $newProps";
	writeLog("$cmd");
	system($cmd);
	checkStatus("Step Name: Instantiate Runtime Data");

}
#---------------------------------------------
# Create new Template file modified targets
#---------------------------------------------

if ($dpType eq "RACPROV")
{
#for extend cluster no new template file and new props are required
if($extend eq "false")
{
    print "Going to create new template file ... \n";
    $cmd="perl $generate_template $templatePath $newTemplate $newProps";
    print "Creating Template File :: CMD = $cmd\n";
    writeLog("$cmd");
    system($cmd);
    checkStatus("Step Name: Create New Template File");

}
else
{
#EXTEND CASE
    my $bak =  $propfilePath . ".new";
    my $cmdt = "cp $propfilePath $bak ";
    system($cmdt);
    my $bak1 = $templatePath . ".new";
    $cmdt = "cp $templatePath $bak1";
    system($cmdt);
    $newProps=$bak;
    $newTemplate=$bak1;
}
#---------------------------------------------
# Replace RuntimeData xml template with values
#---------------------------------------------
print "Going to instantiate runtime data ... \n";

$cmd="perl $replace_runtime $newTemplate $instantiatedTemplate $newProps";
writeLog("$cmd");
system($cmd);
checkStatus("Step Name: Instantiate Runtime Data");
}

# ---------------------------------
# Execute emcli to submit procedure
# ---------------------------------
print "Going to submit DP using emcli ... \n";
#only for debugging - before emcli submit
if($debug eq "true")
{
	sleep($sleepConstant);
}

#frame the schedule, if any
#-schedule="start_time:2006/6/21 21:23;tz:America/New_York"
if($schedule ne "")
{
	$schParam ="\"start_time:".$schedule;
	if($timezone ne "")
	{
		$schParam.=";tz:".$timezone."\"";
	}
	else
	{
		$schParam.="\"";
	}
}

if($srcPath ne "")
{
	if($schedule ne "")
	{
		if($deplInstName ne "")
		{
			$cmd="$srcPath/emcli submit_procedure -instance_name=$deplInstName -procedure=$dpguid -input_file=data:$instantiatedTemplate -schedule=$schParam";
		}
		else
		{
			$cmd="$srcPath/emcli submit_procedure -procedure=$dpguid -input_file=data:$instantiatedTemplate -schedule=$schParam";
		}
	}
	else
	{
		if($deplInstName ne "")
		{
			$cmd="$srcPath/emcli submit_procedure -instance_name=$deplInstName -procedure=$dpguid -input_file=data:$instantiatedTemplate";
		}
		else
		{
		    # $schParam ="\"start_time:".$schedule;
# 		    if($timezone ne "")
# 		    {
# 			$schParam.=";tz:".$timezone."\"";
#		    }
		    $cmd="$srcPath/emcli submit_procedure -procedure=$dpguid -input_file=data:$instantiatedTemplate";
		}		
	    }
}
else
{
	if($schedule ne "")
	{
		if($deplInstName ne "")
		{
		    $cmd="./emcli submit_procedure -instance_name=$deplInstName -procedure=$dpguid -input_file=data:$instantiatedTemplate -schedule=$schParam";
		}
		else
		{
			$cmd="./emcli submit_procedure -procedure=$dpguid -input_file=data:$instantiatedTemplate -schedule=$schParam";
		}
	}
	else
	{
		if($deplInstName ne "")
		{
			$cmd="./emcli submit_procedure -instance_name=$deplInstName -procedure=$dpguid -input_file=data:$instantiatedTemplate";
		}
		else
		{
			$cmd="./emcli submit_procedure -procedure=$dpguid -input_file=data:$instantiatedTemplate";
		}
	}	
}
writeLog("$cmd");
print "CHK+= $cmd";
$instGuid = "";
$instGuid = `$cmd`;
$ret_code = $?;
print "RETURN CODE from EMCLI SUBMIT: ". $ret_code."\n";
writeLog("Instance GUID: ".$instGuid);
#checkStatus("Step Name: Submit Deployment Procedure");
#putting check here itself to avoid any issue
if($ret_code != 0 || $instGuid eq "")
{
	print "Error while executing Step Name: Submit Deployment Procedure \n";
	print "Exiting ...\n";
	exit $ret_code;
}
chomp($instGuid);
print "Instance GUID for DP run is : $instGuid \n";
# ------------------------------------------------------
# Monitor DP execution and wait for the DP to complete
# Note: Monitoring is done only if scheduled immediately
# ------------------------------------------------------
if($schedule eq "" && $donotWait == 0)
{
print "Monitoring DP execution and waiting for its completion ...\n";
if($srcPath ne "")
{
	$cmd="$srcPath/emcli get_instances";
}
else
{
		$cmd="./emcli get_instances";
}
$status="";
	$instName="";
$timeCounter=0;
while($timeCounter < $timeOutCount)
{
	@instList = `$cmd`;
	@instRow = grep(/$instGuid/, @instList);
	foreach $line (@instRow)
	{
		@tmp = split(/,/, $line);
			$instName = $tmp[2];
		$status = $tmp[3];
		chomp($status);
		writeLog("Current Status is : " . $status);
		last;	
	}#end foreach
	$status =~s/^ *//g;
	$status =~s/ *$//g;
		#if we know the DP instance name, use it as a filter
		chomp($instName);
		if(($deplInstName ne "") && ($instName eq $deplInstName))
		{
	if(("$status" ne "Stopped") && ("$status" ne "Failed") && ("$status" ne "Succeeded") && ("$status" ne "Action Required") && ("$status" ne "Completed with Errors"))
	{
		sleep($sleepConstant);
		$timeCounter+=1;
	}
	else
	{
		last;
	}
		}#end filter on dp instance name
		else
		{
			if(("$status" ne "Stopped") && ("$status" ne "Failed") && ("$status" ne "Succeeded") && ("$status" ne "Action Required") && ("$status" ne "Completed with Errors"))
			{
				sleep($sleepConstant);
				$timeCounter+=1;
			}
			else
			{
				last;
			}
		}
}#end while
writeLog("Monitoring DP execution is over. \n");

	#cleanup files created for execution if deleteInstanceData is set to true
	if( $deleteInstanceData eq "true")
	{
		@filelist = ($newProps,$newTemplate,$instantiatedTemplate);
		unlink @filelist;
	}

if($status eq "Succeeded")
{
	print "DP Execution successfully completed. \n";
	exit 0;
}
elsif($timeCounter == $timeOutCount)
{
	print "DP Execution Timed Out. Final DP status is: $status \n";
	print "The current time out is set to " . $timeOutTime/60 . " minute(s). Please increase the time out if necessary. \n";
	exit 1;
}
else
{
	print "DP Execution completed with errors. Final status is: $status \n";
	exit 1;
}
}
elsif($donotWait == 1)
{
	#cleanup files created for execution if deleteInstanceData is set to true
	if( $deleteInstanceData eq "true")
	{
		@filelist = ($newProps,$newTemplate,$instantiatedTemplate);
		unlink @filelist;
	}

        print "DP is submitted.\n";
        exit 0;
}
else # scheduled for later execution, exit with 0
{
	#cleanup files created for execution if deleteInstanceData is set to true
	if( $deleteInstanceData eq "true")
	{
		@filelist = ($newProps,$newTemplate,$instantiatedTemplate);
		unlink @filelist;
	}

	print "DP Execution scheduled at: $schedule. \n";
	exit 0;
}

# ------------------
# Utility Procedures
# ------------------
# Method to check status after command execution
sub checkStatus
{
	my ($command_name)=@_;
	$exit_code = $?;
	if($exit_code != 0)
	{
		print "Error while executing $command_name \n";
		print "Exiting ...\n";
		exit $exit_code;
	}
	print "*** $command_name SUCCESSFUL. ***\n";
}

# Method to write debug logs
sub writeLog
{
	my ($command_name)=@_;
	if($debug eq "true")
	{
		print "$command_name \n";
	}
}

# Method to read command line params
sub parseCommandLineParams()
{
  my $count = $#ARGV;
  while ( $i <= $count )
  {
    $_=$ARGV[$i] ;
    {
      if(/-t/)
      {
        $templatePath = $ARGV[ $i + 1 ];
        $i += 2;
        last;
      }
      elsif(/-p/)
      {
        $propfilePath = $ARGV[ $i + 1 ];
        $i += 2;
        last;
      }
      elsif(/-g/)
      {
        $dpguid = $ARGV[ $i + 1 ];
        $i += 2;
        last;
      }
      elsif(/-s/)
      {
        $temp_date =  $ARGV[ $i + 1 ];
		$temp_time =  $ARGV[ $i + 2 ];
		$schedule = $temp_date. " ". $temp_time;
        $i += 3;
        last;
      }
      elsif(/-z/)
      {
        $timezone = $ARGV[ $i + 1 ];
        $i += 2;
        last;
      }
      elsif(/-d/)
      {
        $srcPath = $ARGV[ $i + 1 ];
        $i += 2;
        last;
      }
      elsif(/-no_wait/)
      {
        $donotWait = 1;
        $i++;
        last;
      }
      elsif(/-n/)
      {
        $deplInstName = $ARGV[ $i + 1 ];
        $i += 2;
        last;
      }
      else { $i++; }
    }
  }#end while
}

#Method to validate schedule
sub validateSchedule()
{
	my ($schedule)=@_;
	#remove extra whitespace between date and time
	$schedule=~s/\s\s+/ /sg;
	writeLog("Schedule in validate method: ". $schedule);

	@schArray = split(/ /, $schedule);
	$fullyear = $schArray[0];
	$fulltime = $schArray[1];

	writeLog("Date: ".$fullyear);
	writeLog("Time: ".$fulltime);

	@yearArray = split(/\//, $fullyear);
	$arraylen = @yearArray;
	if($arraylen != 3)
	{
	   print "Scheduled date does not have yyyy/mm/dd in that format.\n";
	   exit(1);
	}

	$year = $yearArray[0];
	$month = $yearArray[1];
	$day = $yearArray[2];

	if(&checkYear($year) or &checkMonth($month) or &checkDay($day))
	{
		print "The scheduled date contains invalid/non-numeric characters.\nThe proper format is yyyy/mm/dd\n";
		exit(1);
	}

	@timeArray = split(/:/, $fulltime);
	$arraylen = @timeArray;
	if($arraylen != 2)
	{
	   print "Scheduled time does not have HH:mm in that format.\n";
	   exit(1);
	}

	$hr = $timeArray[0];
	$min = $timeArray[1];

	if(&checkHour($hr) or &checkMin($min))
	{
		print "The scheduled time contains invalid/non-numeric characters.\nThe proper format is HH:mm\n";
		exit(1);
	}
	writeLog("The entered schedule is valid.");
}

#Method to check hour format
sub checkHour
{
	my($str)=@_;
	if(&checkIfNumber($str))
	{
	  return 1;
	}
	if($str >= 0 && $str <=24)
	{
	   return 0;
	}
	else
	{
	   writeLog("Hour should be between 1 and 24");
	   return 1;
	}
}

#Method to check minute format
sub checkMin
{
	my($str)=@_;
	if(&checkIfNumber($str))
	{
	  return 1;
	}
	if($str >= 0 && $str <=60)
	{
	   return 0;
	}
	else
	{
	   writeLog("Minute should be between 1 and 60");
	   return 1;
	}
}

#Method to check year format
sub checkYear
{
	my($str)=@_;
	if(&checkIfNumber($str))
	{
	  return 1;
	}
	if(length($str)==4)
	{
	   return 0;
	}
	else
	{
	   writeLog("Year should have 4 digits");
	   return 1;
	}
}

#Method to check month format
sub checkMonth
{
	my($str)=@_;
	if(&checkIfNumber($str))
	{
	  return 1;
	}
	if($str >= 1 && $str <=12)
	{
	   return 0;
	}
	else
	{
	   writeLog("Month should be between 1 and 12");
	   return 1;
	}
}

#Method to check month format
sub checkDay
{
	my($str)=@_;
	if(&checkIfNumber($str))
	{
	  return 1;
	}
	if($str >= 1 && $str <=31)
	{
	   return 0;
	}
	else
	{
	   writeLog("Day should be between 1 and 31");
	   return 1;
	}
}

#Method to checks if a string contains only numeric characters
sub checkIfNumber
{
	my($str)=@_;
	if ($str!~ /\D/)
	{
	  return 0;
	}
	else
	{
	  writeLog("Schedule string contains characters other than digits");
	  return 1;
	}
}
sub trim($)
{
	my $string = shift;
	$string =~ s/^\s+//;
	$string =~ s/\s+$//;
	return $string;
}
