#!/usr/bin/perl # Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. # # NAME # crsconvtoext.pm # # DESCRIPTION # This module contains functions related to converting a cluster to # extended # # NOTES # # # MODIFIED (MM/DD/YY) # jesugonz 08/11/16 24445119: Deconfigure leaf listener resource # jesugonz 07/12/16 23605773: Update param EXTENDED_CLUSTER=TRUE # jesugonz 06/13/16 23526657: update ASM cardinality to 4 # jesugonz 05/13/16 Creation package crsconvtoext; use strict; use File::Basename; use File::Spec::Functions; use File::Copy; # rootcrs modules use crsutils; use crsgpnp; use s_crsutils; use constant CLUSTER_CLASS_STANDALONE => "Standalone"; # Convert to extended object my $CTEXTN; my @CONVERT_TO_EXTENDED_STAGES = ( {"name" => "ClusterChecks", "checkpoint" => "null", "sub" => \&clusterChecks }, {"name" => "ConvertGPNP", "checkpoint" => "null", "sub" => \&convertGPNP }, {"name" => "UpdateParamFile", "checkpoint" => "null", "sub" => \&updateParamFile }, {"name" => "RemoveLeafListenerResource", "checkpoint" => "null", "sub" => \&removeLeafListenerResource }, {"name" => "AddSites", "checkpoint" => "null", "sub" => \&addSitesToConfig }, {"name" => "setASMCardinality", "checkpoint" => "null", "sub" => \&setASMCardinalityExtended }, {"name" => "MapNodeToSite", "checkpoint" => "null", "sub" => \&mapNodeToSite } ); sub new { my ($class, %init) = @_; my @sites = (); $CTEXTN = { "firstnode" => $init{first}, "sites" => $init{sites}, "site" => $init{site}, "siteslist" => \@sites }; bless $CTEXTN, $class; # Delete the no longer needed keys from the init hash # This is needed so crsutils->new won't fail delete $init{first}; delete $init{sites}; delete $init{site}; crsutils->new(%init); convertToExtendedCluster(); } #------------------------------------------------------------------------------- # Function: This function just iterates through all the stages of convert # to extended # Args: # Returns: #------------------------------------------------------------------------------- sub convertToExtendedCluster { foreach my $stage (@CONVERT_TO_EXTENDED_STAGES) { my $name = $stage->{"name"}; my $checkpoint = $stage->{"checkpoint"}; my $func = $stage->{"sub"}; trace("Executing the [$name] step with checkpoint [$checkpoint] ..."); &$func(); } trace("Convert to extended has completed all steps successfully"); } #------------------------------------------------------------------------------- # Function: This function performs some sanity checks that need to pass # in order to allow the convert to extended to continue. # The checks performed are: # 1- Verify the options passed to the script are valid inputs # 2- Verify the script is being executed as super user. # 3- Verify Clusterware stack is fully up # Args: # Returns: #------------------------------------------------------------------------------- sub clusterChecks { my $first_node = $CTEXTN->{firstnode}; # Check if run as superuser if (!check_SuperUser()) { die(dieformat(1)); } # Check options passed # subroutine dies in case of error check_arguments(); # Check Clusterware stack is up if ($first_node) { my $crs_home = getCrsHome(); my @nodes = get_olsnodes_info($crs_home); my $rc = shift @nodes; if ($rc != 0) { die(dieformat(5102, $CFG->{HOST})); } foreach my $node (@nodes) { if (!checkClusterwareOnNode($node)) { die(dieformat(5102, $node)); } } } else { if (checkServiceDown('cluster')) { die(dieformat(5102, $CFG->{HOST})); } } # Make sure CRS active version is at least 12.2 my $crs_version = join('.', get_crs_version()); if (-1 == versionComparison($crs_version, "12.2.0.0.0")) { trace("The current CRS active version is less than 12.2.0.0.0"); die(dieformat(5101)); } # check that cluster class is STANDALONE my $cluster_class = getClusterClass_crsctl(); if ($cluster_class ne CLUSTER_CLASS_STANDALONE) { trace("cluster class is $cluster_class. It needs to be ". CLUSTER_CLASS_STANDALONE); die(dieformat(5101)); } # Dies internally in case of error checkLeafNodes(); trace("Cluster checks have passed"); } #------------------------------------------------------------------------------- # Function: This function verifies the options passed to the script are valid # inputs. # Args: # Returns: #------------------------------------------------------------------------------- sub check_arguments { my $sites = $CTEXTN->{sites}; my $nodesite = $CTEXTN->{site}; my $first_node = $CTEXTN->{firstnode}; if ($first_node) { # If run as first node, the other two options must have a value die(dieformat(5105, '-sites')) if (!defined($sites)); die(dieformat(5105, '-site')) if (!defined($nodesite)); my @sites = split(/\,/, $sites); @sites = uniq(@sites); my $total_sites = scalar(@sites); # check that total number of sites passed is less than the maximum # As of 12.2.0.1.0 the maximum sites is 3 if ($total_sites > 3) { die(dieformat(5106, $total_sites, 3)); } # Check that the site names passed are all valid site names foreach my $site (@sites) { if (!isValidSiteName($site)) { die(dieformat(5107, $site)); } } # Check that the node site passed exists in the list of sites if (scalar(grep(/^$nodesite$/, @sites)) < 1) { die(dieformat(5105, '-site')); } # Add sites to list of sites in ctextn object push(@{$CTEXTN->{siteslist}}, @sites); } else { # If not run as first node, only the -site option must have a value die(dieformat(5105, '-site')) if (!defined($nodesite)); die(dieformat(5105, '-sites')) if (defined($sites)); } trace("Pre-checks have passed"); } #------------------------------------------------------------------------------- # Function: This function checks the site name passed is valid. A valid site # must be: # 1- At least one character but no more than 15 characters in length. # 2- The site name must be alphanumeric. # 3- It cannot begin with a numeric character # 4- It may contain hyphen (-) characters. However, it cannot begin or # end with a hyphen (-) character. # Args: the site name # Returns: TRUE if site name is valid, FALSE otherwise #------------------------------------------------------------------------------- sub isValidSiteName { my ($site) = @_; my $valid = TRUE; my $sitelength = length($site); if ($sitelength > 15 || $sitelength < 1) { $valid = FALSE; } $site = uc($site); # Convert to upper case if ($site !~ /^[A-Z]([A-Z0-9-]?[A-Z0-9])*$/) { $valid = FALSE; } return $valid; } #------------------------------------------------------------------------------- # Function: Helper function to delete duplicated elements in an array. # Args: An array # Returns: The array without duplicated elements #------------------------------------------------------------------------------- sub uniq { my %tmp; foreach (@_) { $tmp{$_} = $_; } return keys %tmp; } #--------------------------------------------------------------------- # Function: checks if there are leaf nodes in the cluster # Args : # Returns : # Notes : Dies in case of error #--------------------------------------------------------------------- sub checkLeafNodes { my @output; my $crsctl = crs_exec_path('crsctl'); my @cmd = ($crsctl, 'get', 'node', 'role', 'config', '-all'); my $rc = run_as_user2($CFG->params('ORACLE_OWNER'), \@output, @cmd); if ($rc != 0) { trace("Failed to run command @cmd, output: @output"); die(dieformat(5101)); } foreach my $line (@output) { chomp $line; if ($line =~ /^Node '(.+)' configured role is 'leaf'$/) { my $leaf_node = $1; die(dieformat(5111, $leaf_node)); } } trace("Leaf nodes check has passed"); } #------------------------------------------------------------------------------- # Function: This function converts the GPNP profile to extended. # It only runs in the first node. # If the GPNP profile is already extended, it just returns. # Args: # Returns: #------------------------------------------------------------------------------- sub convertGPNP { my $first_node = $CTEXTN->{firstnode}; my $orauser = $CFG->params('ORACLE_OWNER'); my @gpnptool_out = (); my $crs_home = $CFG->ORA_CRS_HOME; if (!$first_node) { trace("Skipping GPNP profile conversion, this is not the first node"); return; } if (isClusterExtended()) { trace("The cluster is already extended"); return; } # setup GPNP variables to query from profile using gpnptool. verify_gpnp_dirs($crs_home, $CFG->params('GPNPGCONFIGDIR'), $CFG->params('GPNPCONFIGDIR'), $CFG->HOST, $CFG->params('ORACLE_OWNER'), $CFG->params('ORA_DBA_GROUP')); my $peer_file = get_peer_profile_file(1); # 1 is for local node my $peer_dir = dirname($peer_file); my @ppars = ( '-prf_sq' ); my @pvals = run_gpnptool_getpval($peer_file, \@ppars, $orauser); my $seq_num = $pvals[1] + 1; my @gpnptool_args = ('edit', "-p=\"$peer_file\"", "-asm_ext=\"true\"", "-prf_sq=$seq_num", "-o=\"$peer_dir/profile_ext.xml\"" ); my $rc = run_gpnptool(\@gpnptool_args, $orauser, \@gpnptool_out); if (0 != $rc) { trace("Failed to update GPnP peer profile $peer_file"); print_error("143", "update", $rc); die(dieformat(5101)); } @gpnptool_args = ( 'sign', "-p=\"$peer_dir/profile_ext.xml\"", "-o=\"$peer_dir/profile_ext_sign.xml\"" ); $rc = run_gpnptool(\@gpnptool_args, $orauser, \@gpnptool_out); if (0 != $rc) { trace("Failed to sign the GPnP peer profile $peer_file"); print_error("143", "sign", $rc); die(dieformat(5101)); } # Back up the old profile.xml before overwriting it (better safe than sorry) my $stamp = join('_', localtime()); if (!copy_file($peer_file, "$peer_dir/profile_backup_$stamp.xml", $CFG->params('ORACLE_OWNER'), $CFG->params('ORA_DBA_GROUP'))) { trace("Failed to back up peer profile $peer_file"); die(dieformat(5101)); } # Implement the new profile if (!move("$peer_dir/profile_ext_sign.xml", $peer_file)) { trace("Failed to overwrite $peer_file. $!"); print_error("143", "write", $rc); die(dieformat(5101)); } @gpnptool_args = ('put', "-p=\"$peer_file\""); $rc = run_gpnptool(\@gpnptool_args, $orauser, \@gpnptool_out); if (0 != $rc) { trace("Failed to put peer profile $peer_file "); print_error("143", "update", $rc); die(dieformat(5101)); } trace("GPnP profile was successfully converted to extended."); } #------------------------------------------------------------------------------- # Function: This function adds the list of sites to the cluster configuration. # If not running as the first node, this step is skipped. # If a site passed is already added, it is just skipped, no need to # error out or die. # Args: # Returns: #------------------------------------------------------------------------------- sub addSitesToConfig { my @sites = @{$CTEXTN->{siteslist}}; my $total_sites = scalar(@sites); my $crsctl = crs_exec_path('crsctl'); my $first_node = $CTEXTN->{firstnode}; my @output; if (!$first_node) { trace("Not the first node, skip configuring sites"); return; } trace("Adding $total_sites sites to configuration"); foreach my $site (@sites) { if (isSiteConfigured($site)) { trace("Site $site already configured, skipping"); next; } my @cmd = ($crsctl, 'add', 'cluster', 'site', $site); # This command needs to run as GI USER my $rc = run_as_user2($CFG->params('ORACLE_OWNER'), \@output, @cmd); if ($rc != 0) { trace("An error occurred while adding site $site. Output: @output"); die(dieformat(5103, $site)); } trace("Site $site was configured successfully"); } trace("Sites successfully added to configuration"); } #------------------------------------------------------------------------------- # Function: This function associates the local node to a given site. # If the cluster is not extended it dies. # If the site passed is not in the OCR/OLR it dies. # If the local node is already associated to the given site it # dies (This to avoid performing a Clusterware Services restart that # is not needed) # Args: # Returns: #------------------------------------------------------------------------------- sub mapNodeToSite { my $site = $CTEXTN->{site}; my $node = $CFG->HOST; if (!isClusterExtended()) { die(dieformat(5108)); } if (!isSiteConfigured($site)) { die(dieformat(5109, $site)); } # Check if already mapped to avoid restarting clusterware stack if (isNodeMappedToSite($site)) { trace("Node $node already mapped to site $site. Skipping"); return; } trace("Adding local node $node to site $site in OCR"); my $crsctl = crs_exec_path('crsctl'); my @cmd = ($crsctl, 'modify', 'cluster', 'site', $site, '-n', $node); my @out = system_cmd_capture(@cmd); my $rc = shift @out; if ($rc != 0) { trace("An error occurred while mapping node $node to site $site in OCR"); die(dieformat(5104, $node, $site)); } trace("Adding local node $node to site $site in OLR"); @cmd = ($crsctl, 'modify', 'cluster', 'site', $site, '-l'); @out = system_cmd_capture(@cmd); $rc = shift @out; if ($rc != 0) { trace("An error occurred while mapping node $node to site $site in OLR"); die(dieformat(5104, $node, $site)); } sleep 5; # Give it time to Synchronize # Dies if something goes wrong restartClusterwareStack(); trace("local node $node successfully mapped to site $site"); } #------------------------------------------------------------------------------- # Function: This function restarts the clusterware stack on the local node # Args: # Returns: #------------------------------------------------------------------------------- sub restartClusterwareStack { # This is needed so below subroutines can work my $crs_home = $CFG->ORA_CRS_HOME; $CFG->compCHM(oraClusterwareComp::orachm->new("CHM")); # crsctl stop crs -f stopClusterware($crs_home, 'crs') or die(dieformat(191)); # starts ohasd startOhasdOnly() or die(dieformat(117)); # starts all crs stack resources one by one start_clusterware(START_STACK_ALL) == SUCCESS or die(dieformat(117)); trace("Successfully restarted clusterware stack on local node $CFG->{HOST}"); } #--------------------------------------------------------------------- # Function: Updates ASM cardinality to 4 ASM instances if set to lower # Args : # Returns : # Notes : Dies in case of error while updating instance count #--------------------------------------------------------------------- sub setASMCardinalityExtended { my $new_count = 4; my $run_as_owner = TRUE; my $curr_count = getASMInstanceCount(); # count = -1 means cardinality is set to 'ALL' if ($curr_count == -1 || $curr_count >= 4) { trace("ASM Cardinality already updated (count=$curr_count). Skipping."); return; } my $status = srvctl($run_as_owner, "modify asm -count $new_count", $CFG->ORA_CRS_HOME); if (!$status) { trace("Could not set the ASM Cardinality to $new_count"); die(dieformat(5110, $new_count)); } trace("Successfully updated ASM cardinality"); } #--------------------------------------------------------------------- # Function: Sets EXTENDED_CLUSTER parameter to TRUE # Args : # Returns : # Notes : Dies in case of error #--------------------------------------------------------------------- sub updateParamFile { my $param = "EXTENDED_CLUSTER"; my $value = "true"; if(lc($CFG->params('EXTENDED_CLUSTER')) eq CLUSTER_EXTENDED) { trace("Param file already updated. Skipping"); return; } # Dies internally in case of error modifyparamfile($param, $value, $CFG->paramfile); trace("Successfully updated parameter file"); } #--------------------------------------------------------------------- # Function: Deconfigures Leaf listener resource # Args : # Returns : # Notes : Dies in case of error #--------------------------------------------------------------------- sub removeLeafListenerResource { my $run_as_owner = FALSE; my $listener = 'LISTENER_LEAF'; # If leaf listener is not configured, do nothing. if (!isListenerConfigured($listener, FALSE)) { trace("listener $listener already disabled, skipping step..."); return; } my $status = srvctl($run_as_owner, "stop listener -listener $listener -f", $CFG->ORA_CRS_HOME); # If it is already stopped $status is set to TRUE and check below pass if (!$status) { # non-fatal error trace("Could not stop leaf listener $listener"); } my $status = srvctl($run_as_owner, "remove listener -listener $listener -f", $CFG->ORA_CRS_HOME); if (!$status) { # non-fatal error continue with other steps trace("Could not remove leaf listener $listener"); return; } trace("Leaf listener resource successfully removed"); } 1; # to keep Perl happy