# 
# $Header: has/utl/crsrename.pl /main/5 2016/06/17 12:23:12 jgrout Exp $
#
# crsrename.pl
# 
# Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      crsrename.pl - Utility to rename cluster
#
#    DESCRIPTION
#      This utility executes a sequence of commands used to rename a cluster
#
#    NOTES
#    GNS related operations:
#    --- Check whether GNS is enabled
#    --- Fetch the scan name
#    --- Add CNAME, scanname.oldcluname.domain CNAME scanname.newcluname.domain
#
#    GPNP profile update and bring stack down thereafter:
#    --- crsctl set cluster name <new_clustername>
#
#    MODIFIED   (MM/DD/YY)
#    jgrout      06/06/16 - Added check for QoS being configured and in use
#    dbagaria    04/28/16 - Added check for cluster name validations
#                           Added checks for privileged user
#    ekucukog    04/26/16 - Added check for if RHPS or RHPC is configured
#    dbagaria    04/19/16 - Added check for cluster class "standalone cluster"
#    dbagaria    03/02/16 - Added SIHA check, GNS enabled check, trace support
#    dbagaria    02/03/16 - Creation
# 


package ocrc;

use File::Spec::Functions;

# Windows only inclusions 
use if ($^O eq "MSWin32" || $^O eq "Windows_NT"), Win32;
use if ($^O eq "MSWin32" || $^O eq "Windows_NT"), Win32::NetAdmin => qw(DOMAIN_ALIAS_RID_ADMINS GetAliasFromRID LocalGroupIsMember GroupIsMember);
                     
my $count_args = $#ARGV + 1;

$incorrect_usage = 0;

$oraclehome = $ENV{'ORA_CRS_HOME'}; 
$oraclebase = $ENV{'ORACLE_BASE'}; 

# get host name
$crsctlgethn = catfile($oraclehome, 'bin', 'crsctl get hostname');
open(CRSCTLGETHN, "$crsctlgethn|" );
my @crsctlgethnop = <CRSCTLGETHN>;
close(CRSCTLGETHN);

$hostname = "@crsctlgethnop";
chomp $hostname;

# create log file name
$logfile = catfile($oraclebase, 'diag', 'crs', $hostname, 'crs', 'trace', 'crsrename.trc');

# input validations
inputValidations();

# prechecks
preChecks();

# TODO: Check uniqueness for the new_clustername
$newcname = $ARGV[1];

trace("The new cluster name is $newcname");

# Get the SRVCTL binary
$srvctl = catfile($oraclehome, 'bin', 'srvctl');

# Tasks to be performed before cluster name changed in profile
$do_not_update_clustername = preClusterNameChangeTasks();

if ($do_not_update_clustername == 0)
{
  $crsctlsetclu = catfile($oraclehome, 'bin', 'crsctl set cluster name');
  $crsctlsetcluname = $crsctlsetclu . ' ' . $newcname;
  trace("Executing $crsctlsetcluname");
  open(CRSCTLSETCLUNAME, "$crsctlsetcluname|" );
  my @crsctloutput = <CRSCTLSETCLUNAME>;
  close(CRSCTLSETCLUNAME);

  $exit_value = $? >> 8;
  $crsctloutputstr = "@crsctloutput";
  trace("The crsctl set cluster name output is $crsctloutputstr");
  if ($exit_value != 0)
  {
    $do_not_update_clustername = 1;
  }
}

# Construct the success or failure message and dump it
if ($do_not_update_clustername == 1)
{
  trace("cluster rename failed");
  showErrMsg(42005);
}
else
{
  trace("cluster rename succeeded");

  postClusterNameChangeTasks();
  
  showErrMsg(42004);
}

# Any new functions go below this section

# Function to write traces
sub trace
{
  my ($sec, $min, $hour, $day, $month, $year) =
      (localtime) [0, 1, 2, 3, 4, 5];
  $month = $month + 1;
  $year = $year + 1900;

  if ($logfile) 
  {
    open (my $TRCFILE, ">>$logfile");
    if ($TRCFILE)
    {
      printf $TRCFILE  "%04d-%02d-%02d %02d:%02d:%02d: @_\n",
        $year, $month, $day, $hour, $min, $sec;
      close ($TRCFILE);
    }
  }
}

# Input validation
sub inputValidations
{
  if ($count_args != 2) 
  {
    $incorrect_usage = 1;
  }

  if ($ARGV[0] ne "cluster")
  {
    trace("Incorrect usage: first argument is $ARGV[0] instead of cluster");
    $incorrect_usage = 1;
  }

  if ($incorrect_usage == 1)
  {
    showErrMsg(42001);
    exit 1;
  }
}

# All pre checks for cluster rename 
sub preChecks()
{
  $incorrect_usage = validateClusterName($ARGV[1]);

  # If not super user then return
  $suser = checkSuperUser();
  if (!$suser)
  {
    showErrMsg(9082);
    exit 1;
  }

  # Check if it is SIHA and if so exit
  $olsnodes = catfile($oraclehome, 'bin', 'olsnodes');
  trace("Executing $olsnodes -v");
  open(OLSNODESV, "$olsnodes -v|");
  my @olsnodesvoutput = <OLSNODESV>;
  close(OLSNODESV);

  $olsnodesvstring = "@olsnodesvoutput";
  if ($olsnodesvstring =~ m/SIHA/)
  {
    trace("olsnodes output states that it is SIHA configuration");
    showErrMsg(42002);
    exit 1;
  }

  # Check if it is a standalone cluster and if not exit
  $getcluclass = catfile($oraclehome, 'bin', 'crsctl get cluster class');
  open(GETCLUCLASS, "$getcluclass|" );
  my @getcluclassop = <GETCLUCLASS>;
  close(GETCLUCLASS);
  
  $getcluclassstring = "@getcluclassop";

  if ($getcluclassstring =~ m/Standalone Cluster/)
  {
    trace("Its a standalone cluster");  
  } 
  else
  {
    trace("Its not a standalone cluster");
    showErrMsg(42003);
    exit 1;
  }

  #precheck for if RHP Server or RHP Client is configured
  checkRHPConfigured();
 
  #precheck if QoS is configured and in use
  checkQoSIsInUse();
}

sub checkRHPConfigured()
{ 
 
  # Check if RHP Server or RHP Client is configured, and if so exit 
  $configrhps = catfile($oraclehome, 'bin', 'srvctl config rhpserver');
  $configrhpc = catfile($oraclehome, 'bin', 'srvctl config rhpclient');
  
  trace("Executing $configrhps");
  open(CONFIGRHPS, "$configrhps|" );
  my @configrhpsoutput = <CONFIGRHPS>;
  close(CONFIGRHPS);

  $exit_value_rhps = $? >> 8;
  $configrhpsoutputstr = "@configrhpsoutput";
  trace("Output for srvctl config rhpserver is  $configrhpsoutputstr");

  trace("Executing $configrhpc");
  open(CONFIGRHPC, "$configrhpc|" );
  my @configrhpcoutput = <CONFIGRHPC>;
  close(CONFIGRHPC);

  $exit_value_rhpc = $? >> 8;
  $configrhpcoutputstr = "@configrhpcoutput";
  trace("Output for srvctl config rhpclient is  $configrhpcoutputstr");

  if ($exit_value_rhps == 0 || $exit_value_rhpc == 0 )
  {
    trace("RHP Server or RHP Client is configured");
    showErrMsg(42007);
    exit 1;
  }
  else
  {
    trace("Neither an RHP Server nor an RHP Client is configured on this cluster.");
  } 
}

sub checkQoSIsInUse()
{ 
  # Check if QoS is configured and in use, and if so, exit

  my $ocrdump = catfile($oraclehome, 'bin', 'ocrdump');
  my $ocrdumpwlm = "$ocrdump -stdout -keyname SYSTEM.WLM.POLICYSET " .
                   "-xml -noheader";
  trace("Executing $ocrdumpwlm");
  
  open(my $ocrfh, '-|', $ocrdumpwlm);

  # Process the WLM Policyset key data (must read
  # all the data to avoid SIGPIPE or equivalent)
  #
  # State machine determines if the two XML output header lines
  # for key SYSTEM.WLM.POLICYSET are seen consecutively, which
  # means that QoS is in use
  #
  # State 0 - Miscellaneous state (Entry)
  # State 1 - First XML output line just seen
  # State 2 - XML output header lines seen together (Terminal)
  #
  my $state = 0;
  while (<$ocrfh>) {
    # If in terminal state, keep reading
    if ($state == 2) {
      next;
    }
    chomp;
    # If we find the first of two XML output header lines,
    # transition to state 1
    if ($_ eq '<KEY>') {
      $state = 1;
    }
    elsif (($state == 1) &&
           ($_ eq '<NAME>SYSTEM.WLM.POLICYSET</NAME>')) {
      # Remember that QoS is in use
      $state = 2;
    }
    else {
      # Return to miscellaneous state
      $state = 0;
    }
  }      
  close $ocrfh;
  
  if ( $state == 2 )
  {
    trace("Quality of Service Management is in use");
    showErrMsg(42008);
    exit 1;
  }
  else
  {
    trace("Quality of Service Management is not in use");
  } 
}

sub preClusterNameChangeTasks()
{
  $retVal = addScanAliasToGns();

  return $retVal;
}

sub addScanAliasToGns()
{
  $retVal = 0;

  # if GNS is not enabled then no need to create alias
  if (!isGNSEnabled())
  {
    return 0;
  }

  # srvctl config scan to fetch the scan name
  $srvctlconfigscan = $srvctl . ' config scan | head -1';

  trace("Executing $srvctlconfigscan");
  open(SRVCTLCONFIGSCAN, "$srvctlconfigscan|");
  my @srvctlconfigoutput = <SRVCTLCONFIGSCAN>;
  close(SRVCTLCONFIGSCAN);

  $exit_value = $? >> 8;

  # If "srvctl config scan" failed then abort
  if ($exit_value != 0)
  {
    return 1;
  }

  $configscanopstring = "@srvctlconfigoutput";
 
  # If the scan name is present then create the alias
  if($configscanopstring =~ m/:(.*?),/) 
  {
    $fqdn = $1;

    $sname = $fqdn;

    # Get cluster name in format .clustername.
    $cname = '.' . getClusterName() . '.';
  
    # If cluster name not part of scan name then no alias needed
    $pos = index($sname, $cname);
    if ($pos < 0)
    {
      trace("The cluster name $cname is not sub-string of scanname $sname");
      return 0;
    }

    my @subs = split /\./, $fqdn, 3;

    $scanname = $subs[0] . '.';
    $clustername = $subs[1];
    $gnsdomain = $subs[2];

    $newclustername = $newcname . '.';
    $srvctlupdategns = $srvctl . ' update gns';  

    # Construct the "srvctl update gns -alias command
    $srvctlcfgupdalias = $srvctlupdategns . ' -name ' . $fqdn . ' -alias ' . $scanname . $newclustername . $gnsdomain;

    trace("Executing $srvctlcfgupdalias");
    open(NEWCOMMAND, "$srvctlcfgupdalias|");
    my @srvctlcfgupdaliasoutput = <NEWCOMMAND>;
    close(NEWCOMMAND);

    $exit_value = $? >> 8;
    $srvctlcfgupdaliasoutputstr = "@srvctlcfgupdaliasoutput";
    trace("The srvctl alias update output is $srvctlcfgupdaliasoutputstr");
    if ($exit_value != 0)
    {
      return 1;
    }
  }

  return 0;
}

sub postClusterNameChangeTasks
{
  return;
}

sub showErrMsg
{
    $msg = catfile($oraclehome, 'bin', 'clsecho -p crs -f crs -m ');
    $errmsg = $msg . ' ' . $_[0];

    open(ERRMSG, "$errmsg|");
    my @errmsgoutput = <ERRMSG>;
    close(ERRMSG);
    print "@errmsgoutput";
}

sub getClusterName
{
  $olsnodesc = catfile($oraclehome, 'bin', 'olsnodes -c');
  open(OLSNODESC, "$olsnodesc|" );
  my @olsnodescop = <OLSNODESC>;
  close(OLSNODESC);

  $clustername = "@olsnodescop";
  chomp $clustername;
  return $clustername;
}

sub isGNSEnabled()
{
  $is_gns_enabled = 0;
  # srvctl config gns to check whether GNS is enabled
  $srvctlconfiggns = $srvctl . ' config gns -S 1';

  trace("Executing $srvctlconfiggns");
  open(SRVCTLCONFIGGNS, "$srvctlconfiggns|");
  my @srvctlconfiggnsoutput = <SRVCTLCONFIGGNS>;
  close(SRVCTLCONFIGGNS);

  my $exit_value;
  $exit_value = $? >> 8;

  if ($exit_value == 0)
  {
    $configgnsopstring = "@srvctlconfiggnsoutput";
    trace("The srvctl config gns output is $configgnsopstring ");

    if ($configgnsopstring =~ m/enabled=\{true\}/)
    {
      $is_gns_enabled = 1;
    }
  }
  # Failure in "srvctl config gns"; bail out
  else
  {
  # Confirm with SAM about the return codes from SRVCTL
  #  $retVal = 1;
  }
  return $is_gns_enabled;
}


sub validateClusterName
{
  $retval = 0;
  $string = $_[0];

  if ($string =~ m/[^a-zA-Z0-9\-]/)
  {
    trace("Cluster name has invalid charachters : $string");
    $retval = 1;
  }
 
  $strlen = length $string;
  if ($strlen > 15)
  {
    trace("Cluster name too long : $strlen");
    $retval = 1;
  }

  if ($string =~ /^[a-zA-Z]/)
  {
    # Starts with alphabet
  }
  else
  {
    trace("Cluster name starts with non-alphabet : $string");
    $retval = 1;
  }

  if ($retval == 1)
  {
    trace("Cluster name invalid");
    showErrMsg(42006);
    exit 1;
  }
}

sub checkSuperUser
{
  # For windows 
  if( $^O eq "MSWin32" || $^O eq "Windows_NT" )
  {
    trace ("Checking for super user privileges");

    my $superUser = $ENV{'USERNAME'};
    my $groupName = s_getGroupName();
    trace ("superUser=$superUser groupName=$groupName");

    if (! $groupName) 
    {
        return "";
    }

    my $userName   = Win32::LoginName();
    $userName      =~ tr/a-z/A-Z/;
    my $errorMsg   = "User must be \"SYSTEM\", or $userName must be " .
                     "or a member of $groupName group to run root script";

    trace ("user=$userName");

    my $systemName = 'SYSTEM';
    if (! is_dev_env ()) 
    {
       $systemName = s_getSystem();
       $systemName =~ tr/a-z/A-Z/;
    }

    if ($userName eq $systemName) 
    {
       trace ("User has $superUser privileges");
       return $superUser;
    }

    if (!(LocalGroupIsMember("", $groupName, $userName) ||
          GroupIsMember("", $groupName, $userName))) 
    {
       
      $userName = $CFG->params('ORACLE_OWNER');
      trace ("verifying Admin privilege for user=$userName");

      if (!(LocalGroupIsMember("", $groupName, $userName) ||
            GroupIsMember("", $groupName, $userName))) 
      { 
        error ("$errorMsg");
        return "";
      }
    }

    trace ("User has $superUser privileges");
    return $superUser;
  }
  # Check for all other platforms
  else
  {
    my $superUser = "root";
    my $program   = "this script";
    my $usrname = getpwuid ($<);
 
    my $uid = getpwnam ($usrname);
    if ($uid ne 0) 
    {
      trace ("You must be logged in as $superUser with its UID being '0' " .
             "to run $program.");
      return "";
    }
    return $superUser;
  }
}

sub s_getGroupName
{
  my $groupName;
  if (! GetAliasFromRID("", DOMAIN_ALIAS_RID_ADMINS, $groupName))
  {
    trace("GetAliasFromRID failed");
  }
  return $groupName;
}

sub is_dev_env
{
  my $isDevEnv = uc($ENV{'_SCLS_DEVELOPMENT'});
  if ($isDevEnv eq "TRUE")
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}
