# # dgcreate.pl # # Copyright (c) 2002, 2005, Oracle. All rights reserved. # # NAME # dgcreate.pl # # DESCRIPTION # Data Guard physical/logical post-DBClone standby creation # # NOTES # # # MODIFIED (MM/DD/YY) # sjconnol 06/17/05 - Bug 4439779 # sjconnol 06/03/05 - Bug 4407481: revert previous changes # sjconnol 05/12/05 - Change to DGexecuteSQLPlus # sjconnol 08/30/04 - Logical auto delete # sjconnol 08/18/04 - OMF/ASM changes # sjconnol 08/10/04 - Change mount command for 10g standby # sjconnol 08/03/04 - Change addTempfiles to addTempfilesDG # sjconnol 07/29/04 - Move tempfile deletion before database open # sjconnol 07/15/04 - Bug 3736070 # sjconnol 06/28/04 - Don't use conn descriptor in stby connections # sjconnol 06/28/04 - Fix duplicate CLEAR LOGFILE commands # sjconnol 06/14/04 - Bug 3655231 # sjconnol 06/11/04 - Tempfile changes for 10.2 # pbantis 05/18/04 - Exit on SQLPLUS errors. # sjconnol 03/10/04 - Archive log auto deletion # sjconnol 12/03/03 - NT fixes # sjconnol 11/20/03 - Fix indexing in addTempFiles # sjconnol 10/30/03 - Bug 3227245: change phys stby recovery # sjconnol 09/26/03 - Put back use of TNS descriptor for stby connect # sjconnol 09/15/03 - Bug 3130235, 3134578 # sjconnol 06/13/03 - Bug 2964554 # sjconnol 04/29/03 - Bug 2930030 # sjconnol 02/10/03 - Change separator # sjconnol 01/31/03 - Conditionalize last fix for 10i # sjconnol 01/29/03 - Use local dest instead of standby_archive_dest # sjconnol 01/09/03 - Move waitForDG() to dgutil.pl # sjconnol 01/02/03 - Do set of display name after enable # pbantis 12/04/02 - Move handleError/Warning subroutine to dgutil # sjconnol 11/21/02 - Existing backup support # sjconnol 11/17/02 - sjconnol_dg1115 # sjconnol 11/12/02 - Logical 10i support # sjconnol 10/29/02 - RAC compatibility changes # sjconnol 10/15/02 - Creation # require "flush.pl"; use DBI; $TEST_101 = 1; require "$ENV{EMDROOT}/sysman/admin/scripts/db/dg/dgutil.pl"; require "$ENV{EMDROOT}/sysman/admin/scripts/db/rman/rman.pl"; $SIG{__DIE__} = \&handleError; $SIG{__WARN__} = \&handleWarning; $DESTSQL = "select destination from v\$archive_dest where dest_name='STANDBY_ARCHIVE_DEST'"; # Exit on SQLPLUS errors. $IGNORE_SQLPLUS_ERROR = 0; sub setPrmyCreds{ my ($username, $password, $tns) = @_; my $mode = 2; # SYSDBA if(!$tns){ $prmyLDA = DBI->connect('dbi:Oracle:', "$username", "$password", {ora_session_mode => $mode, PrintError => 0, RaiseError => 1}); } else{ ## Make a DBI connection to primary $prmyLDA = DBI->connect('dbi:Oracle:', "$username@".$tns , "$password", {ora_session_mode => $mode, PrintError => 0, RaiseError => 1}); } debug("dgcreate.setPrmyCreds: User Name: $username"); } sub setStbyCreds{ ($stbyUser, $stbyPW, $stbyTNS) = @_; my $mode = 2; # SYSDBA # if(!$stbyTNS){ $stbyLDA = DBI->connect('dbi:Oracle:', "$stbyUser", "$stbyPW", {ora_session_mode => $mode, PrintError => 0, RaiseError => 1}); # } # else{ # $stbyLDA = DBI->connect('dbi:Oracle:', "$stbyUser@".$stbyTNS , "$stbyPW", # {ora_session_mode => $mode, # PrintError => 0, # RaiseError => 1}); # } debug("dgcreate.setStbyCreds: User Name: $stbyUser"); } sub disconnect{ debug("dgcreate.disconnect"); $prmyLDA->disconnect; $stbyLDA->disconnect; } sub createStandby{ debug("dgcreate.createStandby"); my($instance, $ohome, $lfile, $initfile, $islogical, $fromDatafiles, $toDatafiles, $fromLogfiles, $toLogfiles, $logGroups, $tempNames, $tablespaces, $sizes, $autoextends, $dgopts, $newdbname) = @_; ## environment setup createAdmin($instance, $ohome, $lfile, $initfile, $islogical, $dgopts); if($isLogical){ if($is10i){ createLogical10i($fromDatafiles, $toDatafiles, $fromLogfiles, $toLogfiles, $logGroups, $tempNames, $tablespaces, $sizes, $autoextends, $newdbname); } else{ createLogical9i($fromDatafiles, $toDatafiles, $fromLogfiles, $toLogfiles, $logGroups, $tempNames, $tablespaces, $sizes, $autoextends, $newdbname); } } else{ createPhysical($fromDatafiles, $toDatafiles, $fromLogfiles, $toLogfiles, $logGroups, $tempNames, $tablespaces, $sizes, $autoextends); } ## Add the resource and enable addResource(); disconnect(); } sub createPhysical{ debug("dgcreate.createPhysical"); my($fromDatafiles, $toDatafiles, $fromLogfiles, $toLogfiles, $logGroups, $tempNames, $tablespaces, $sizes, $autoextends) = @_; ##init.ora files for physical putIfile(); ## Remote old SPFILE rmInitFiles(); ## bounce standby bounceStby(); ## Rename files renameFiles($fromDatafiles, $toDatafiles, $fromLogfiles, $toLogfiles, $logGroups); ## Harvest the recovery SCN getRecoverySCN(); ## Recover the standby recoverStby($tempNames); ## Move the real init.ora file in place mvIfile(); ## Create the SPFILE createSpfile(); ## Create tempfiles addTempfiles_DG($tempNames, $tablespaces, $sizes, $autoextends); ## bounce standby bounceStby(); ## Online offlined datafiles onlineDatafiles(); ## Setup autodeletion policy ## (Non-essential operation; do not process return val) setAutoDelete(); } ## 9i Logical creation sub createLogical9i{ debug("dgcreate.createLogical9i"); my($fromDatafiles, $toDatafiles, $fromLogfiles, $toLogfiles, $logGroups, $tempNames, $tablespaces, $sizes, $autoextends, $newdbname) = @_; ## Modify init.ora files for physical putIfile($newdbname); ## Remote old SPFILE rmInitFiles(); ## bounce standby, startup mount bounceStby(); ## Rename files renameFiles($fromDatafiles, $toDatafiles, $fromLogfiles, $toLogfiles, $logGroups); ## Harvest the recovery SCN getRecoverySCN(); ## Recover the standby, open resetlogs recoverStby(); ## Create tempfiles addTempfiles_DG($tempNames, $tablespaces, $sizes, $autoextends); ## Start logical apply (9i only) startLogical(); ## bounce, startup mount bounceStby(); ## DB ID/name change steps if(!$devMode){ ## Run NID to change DB ID/name nid($newdbname); ## Move the real init.ora file in place mvIfile(); } ## Create the SPFILE createSpfile(); ## shutdown shutdownStby(); if(!$devMode){ ## Create new orapwd file orapwd(); } ## startup startupStby(); ## Open resetlogs (only if NID was run) if(!$devMode){ openStby(); } } ## 10i Logical creation sub createLogical10i{ debug("dgcreate.createLogical9i"); my($fromDatafiles, $toDatafiles, $fromLogfiles, $toLogfiles, $logGroups, $tempNames, $tablespaces, $sizes, $autoextends, $newdbname) = @_; ## Modify init.ora files for physical putIfile($newdbname); ## Remote old SPFILE rmInitFiles(); ## bounce standby, startup mount bounceStby(); ## Rename files renameFiles($fromDatafiles, $toDatafiles, $fromLogfiles, $toLogfiles, $logGroups); ## Harvest the recovery SCN getRecoverySCN(); ## Recover the standby, open resetlogs recoverStby(); ## Create tempfiles addTempfiles_DG($tempNames, $tablespaces, $sizes, $autoextends); ## bounce, startup mount bounceStby(); ## DB ID/name change steps if(!$devMode){ ## Turn off broker before running nid my($sql) = "alter system set dg_broker_start=false scope=memory"; printDebug("dgcreate.nid: $sql"); my($dbcur) = $stbyLDA->prepare($sql); $dbcur->execute; ## Disconnect before running nid $stbyLDA->disconnect; ## Run NID to change DB ID/name; 10i NID shuts down database nid($newdbname); ## Database is now shutdown; create new password file orapwd(); ## Move the real init.ora file in place mvIfile(); ## Restart startupStby(); } ## Create the SPFILE createSpfile(); ## Bounce bounceStby(); ## Open resetlogs (only if NID was run) if(!$devMode){ openStby(); } ## Setup autodeletion policy ## (Non-essential operation; do not process return val) setAutoDelete(); } sub getStbyDest{ my($stbydest); if($is10i){ my $recarea = get_init_param($stbyLDA, "db_recovery_file_dest"); ## If the recovery area is in use, no need to return ## a value; recovery will automatically use it if(defined($recarea) && ($recarea =~ /\S/)){ debug("dgcreate.getStbyDest: using recovery area: $recarea"); return undef; } ## Otherwise, get the value of standby archive dest my $dbcur = $stbyLDA->prepare($DESTSQL); $dbcur->execute; my @row = $dbcur->fetchrow_array(); $stbydest = $row[0]; } else{ $stbydest = get_init_param($stbyLDA, "standby_archive_dest"); } debug("dgcreate.getStbyDest: standby_archive_dest: $stbydest"); return $stbydest; } sub recoverStby{ my($tempfiles) = @_; my($sql, $dbcur); debug("dgcreate.recoverStby: tempfiles = $tempfiles"); ## For physical, only do recovery if there are tempfiles if(defined($tempfiles) && ($tempfiles eq "$DELIMITER")){ debug("dgcreate.recoverStby: no recovery to be done, returning"); return; } ## 10.2: tempfile entries are retained in stby controlfile; ## run drop cmd for as many entries as are known if($dbVer eq "10.2"){ $sql = "SELECT FILE# from v\$tempfile"; printDebug("dgcreate.recoverStby: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; my @row = $dbcur->fetchrow_array(); for($i = 0; $i <= $#row; $i++){ $sql = "ALTER DATABASE TEMPFILE $row[$i] DROP"; printDebug("dgcreate.recoverStby: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; } } my $fromclause = ""; my $stbydest = getStbyDest(); ## If using recovery area (which will be true for OMF/ASM), no ## need to specify FROM clause if(defined($stbydest)){ $fromclause = "FROM '$stbydest'"; } $sql = "ALTER DATABASE RECOVER AUTOMATIC $fromclause STANDBY DATABASE UNTIL CHANGE $pitSCN"; ## Backup controlfile used only for 9i logical if($isLogical && !$is10i){ $sql = "ALTER DATABASE RECOVER AUTOMATIC FROM '$stbydest' DATABASE UNTIL CHANGE $pitSCN USING BACKUP CONTROLFILE"; } ## Only disable exception handling for physical if(!$isLogical){ ## Want to ignore all ORA-00278, ORA-00279, ORA-00289, ORA-00280, ## ORA-00310 , ORA-00334 errors during this type of recovery, as they ## are normal ## Turn off all exception handling, turn on logging of errors; ## if there's real error (not one of the ones above), we'll ## catch it in the open $stbyLDA->{RaiseError} = 0; $stbyLDA->{PrintError} = 1; } printDebug("dgcreate.recoverStby: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; if($isLogical){ ## 10i logical: ACTIVATE (not open), bounce, open if($is10i){ $sql = "alter database activate standby database"; printDebug("dgcreate.recoverStby: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; bounceStby(); $sql = "ALTER DATABASE OPEN"; printDebug("dgcreate.recoverStby: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; } ## 9i logical: Turn on guard prior to open else{ $sql = "ALTER DATABASE GUARD ALL"; printDebug("dgcreate.recoverStby: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; $sql = "ALTER DATABASE OPEN RESETLOGS"; printDebug("dgcreate.recoverStby: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; } } ## Physical else{ $sql = "ALTER DATABASE RECOVER CANCEL"; printDebug("dgcreate.recoverStby: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; ## Turn exception handling back on $stbyLDA->{RaiseError} = 1; $stbyLDA->{PrintError} = 0; $sql = "ALTER DATABASE OPEN READ ONLY"; printDebug("dgcreate.recoverStby: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; } debug("dgcreate.recovery: recovery complete to $pitSCN"); } sub getRecoverySCN{ my($sql, $dbcur); ## 10i Logical: Recovery SCN is found on the standby, not the primary if($isLogical && $is10i){ $sql = "SELECT DI2LR_SCN FROM X\$KCCDI2"; $dbcur = $stbyLDA->prepare($sql); } ## Otherwise, recovery SCN found on primary else{ if($isLogical){ $sql = "SELECT MAX(FIRST_CHANGE#) FROM V\$ARCHIVED_LOG WHERE DICTIONARY_BEGIN = 'YES'"; } else{ $sql = "SELECT MAX(NEXT_CHANGE#) FROM V\$ARCHIVED_LOG"; #$sql = "SELECT CHECKPOINT_CHANGE# FROM V\$DATABASE"; } $dbcur = $prmyLDA->prepare($sql); } printDebug("dgcreate.getRecoverySCN: $sql"); $dbcur->execute; my @row = $dbcur->fetchrow_array(); $pitSCN = $row[0]; debug("dgcreate.getRecoverySCN: pitSCN: $pitSCN"); } sub getPrmyID{ my $sql = "SELECT DBID from v\$database"; printDebug("dgcreate.getPrmyID: $sql"); my $dbcur = $prmyLDA->prepare($sql); $dbcur->execute; my @row = $dbcur->fetchrow_array(); my($dbID) = $row[0]; debug("dgcreate.getPrmyID: db ID: $dbID"); return($dbID); } sub startLogical(){ debug("dgcreate.startLogical: *** START ***"); my($dbID) = getPrmyID(); my $sql = "Begin DBMS_LOGSTDBY.PRIMARY_DBID($dbID); End;"; printDebug("dgcreate.startLogical: $sql"); my $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; if($is10i){ $sql = "ALTER DATABASE START LOGICAL STANDBY APPLY"; } else{ $sql = "ALTER DATABASE START LOGICAL STANDBY APPLY INITIAL $pitSCN"; } printDebug("dgcreate.startLogical: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; $sql = "ALTER DATABASE STOP LOGICAL STANDBY APPLY"; printDebug("dgcreate.startLogical: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; debug("dgcreate.startupLogical: *** END ***"); } sub onlineDatafiles{ my($file, $dbcur); foreach $file (@OFFLINE_FILES){ my($sql) = "ALTER DATABASE DATAFILE '$file' ONLINE"; printDebug("dgcreate.onlineDatafiles: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; } } sub renameFiles{ my ($fromDatafiles, $toDatafiles, $fromLogfiles, $toLogfiles, $logGroups) = @_; my(@fromDatafiles) = split /$DELIMITER/, $fromDatafiles; my(@toDatafiles) = split /$DELIMITER/, $toDatafiles; my(@fromLogfiles) = split /$DELIMITER/, $fromLogfiles; my(@toLogfiles) = split /$DELIMITER/, $toLogfiles; my(@logGroups) = split /$DELIMITER/, $logGroups; my($from, $dbcur); debug("dgcreate.renameFiles: *** START ***"); ## rename datafiles my($count) = 0; foreach $from (@fromDatafiles){ my($to) = $toDatafiles[$count]; $count++; ## Skip rename if files are the same name ## (Case insensitive match for NT) if(($from eq $to) || ($NT && (lc($from) eq lc($to)))){ debug("dgcreate.renameFiles: Skipping rename for same name: $from $to"); next; } my $sql = "ALTER DATABASE RENAME FILE '$from' to '$to'"; printDebug("dgcreate.renameFiles: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; ## Bug 3736070: If the file is offline on the primary, take it offline ## on the standby so recovery doesn't try to recover it $sql = "SELECT STATUS FROM V\$DATAFILE WHERE NAME = '$from'"; printDebug("dgcreate.renameFiles: $sql"); $dbcur = $prmyLDA->prepare($sql); $dbcur->execute; my @row = $dbcur->fetchrow_array(); if($row[0] =~ /OFFLINE|RECOVER/i){ printDebug("dgcreate.renameFiles: datafile $from is offline on primary; taking $to offline on standby"); push(@OFFLINE_FILES, $to); $sql = "ALTER DATABASE DATAFILE '$to' OFFLINE DROP"; printDebug("dgcreate.renameFiles: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; } } $count = 0; foreach $from (@fromLogfiles){ my($to) = $toLogfiles[$count]; $count++; ## Skip rename if files are the same name ## (Case insensitive match for NT) if(($from eq $to) || ($NT && (lc($from) eq lc($to)))){ debug("dgcreate.renameFiles: Skipping rename for same name: $from $to"); next; } my $sql = "ALTER DATABASE RENAME FILE '$from' to '$to'"; printDebug("dgcreate.renameFiles: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; } debug("dgcreate.renameFiles: Data/log file rename complete"); my($group, %donegroups); foreach $group (@logGroups){ if(defined($donegroups{$group})){ printDebug("dgcreate.renameFiles: already processed group $group"); next; } $donegroups{$group} = 1; my $sql = "ALTER DATABASE CLEAR LOGFILE GROUP $group"; printDebug("dgcreate.renameFiles: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; } debug("dgcreate.renameFiles: Log groups created"); } ## Shutdown the standby sub shutdownStby { debug("dgcreate.shutdownStby: *** START ***"); ## disconnect $stbyLDA->disconnect; push(@SQLCMDS, "shutdown immediate"); #executeSQLPlusSYSDBA($stbyUser, $stbyPW, $stbyTNS); executeSQLPlusSYSDBA($stbyUser, $stbyPW, ''); debug("dgcreate.shutdownStby: *** END ***"); undef @SQLCMDS; } ## Shutdown the standby abort sub shutdownStbyAbort { debug("dgcreate.shutdownStbyAbort: *** START ***"); ## disconnect $stbyLDA->disconnect; push(@SQLCMDS, "shutdown abort"); #executeSQLPlusSYSDBA($stbyUser, $stbyPW, $stbyTNS); executeSQLPlusSYSDBA($stbyUser, $stbyPW, ''); debug("dgcreate.shutdownStbyAbort: *** END ***"); undef @SQLCMDS; } ## Open resetlogs (logical only) sub openStby { debug("dgcreate.openStby: *** START ***"); push(@SQLCMDS, "ALTER DATABASE OPEN RESETLOGS"); #executeSQLPlusSYSDBA($stbyUser, $stbyPW, $stbyTNS); executeSQLPlusSYSDBA($stbyUser, $stbyPW, ''); debug("dgcreate.openStby: *** END ***"); undef @SQLCMDS; } ## Startup the standby ## mount for logical ## nomount for physical sub startupStby { debug("dgcreate.startupStby: *** START ***"); if($is10i || $isLogical){ push(@SQLCMDS, "startup mount"); } else{ push(@SQLCMDS, "startup nomount"); push(@SQLCMDS, "ALTER DATABASE MOUNT STANDBY DATABASE"); } #executeSQLPlusSYSDBA($stbyUser, $stbyPW, $stbyTNS); executeSQLPlusSYSDBA($stbyUser, $stbyPW, ''); ## Reconnect to standby setStbyCreds($stbyUser, $stbyPW, $stbyTNS); debug("dgcreate.startupStby: *** END ***"); undef @SQLCMDS; } sub bounceStby{ shutdownStby(); startupStby(); } sub createSpfile(){ debug("dgcreate.createSpfile: *** START ***"); my $sql = "CREATE SPFILE FROM PFILE='$Ifile'"; printDebug("dgcreate.createSpfile: $sql"); my $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; debug("dgcreate.createSpfile: *** END ***"); } sub addTempfiles_DG{ debug("dgcreate.addTempfiles: *** START ***"); my ($fileNames, $tablespaces, $sizes, $autoextends) = @_; if($fileNames eq "$DELIMITER"){ debug("dgcreate.addTempfiles: NO TEMPFILES TO ADD, returning"); return; } my @tempTablespaces = split /$DELIMITER/, $tablespaces; my @tempFileNames = split /$DELIMITER/, $fileNames; my @tempSizes = split /$DELIMITER/, $sizes; my @tempAutoextends = split /$DELIMITER/, $autoextends; my $tempFileNames; my $index = 0; my($sql, $dbcur); foreach $tempFileNames (@tempFileNames) { $sql = "ALTER TABLESPACE $tempTablespaces[$index] ADD TEMPFILE '$tempFileNames' SIZE $tempSizes[$index] REUSE $tempAutoextends[$index]"; printDebug("dgcreate.addTempfiles: $sql"); $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; $index++; } debug("dgcreate.addTempfiles: *** END ***"); } sub addResource{ my($state); if($is10i){ $state = "PHYSICAL"; if($isLogical){ $state = "LOGICAL"; } } else{ $state = "PHYSICAL-APPLY-ON"; if($isLogical){ $state = "LOGICAL-APPLY-ON"; } } my($indoc); ## For 10i, first remove the site (and database), then re-add it ## so that the property harvest happens if($is10i){ $indoc = ""; debug("dgcreate.addResource: $indoc"); processStatus(get_dg_document($prmyLDA, $indoc)); debug("dgcreate.addResource: Database $resName removed"); $indoc = ""; debug("dgcreate.addResource: $indoc"); processStatus(get_dg_document($prmyLDA, $indoc)); debug("dgcreate.addResource: Database $resName re-added"); } ## For 9i, add the resource else{ $indoc = ""; debug("dgcreate.addResource: $indoc"); processStatus(get_dg_document($prmyLDA, $indoc)); debug("dgcreate.addResource: Resource $resName added"); } ## Turn off all exception handling on the primary, turn on logging of errors; ## we don't want to error the whole job because of an enable problem $prmyLDA->{RaiseError} = 0; $prmyLDA->{PrintError} = 1; $indoc = ""; ## Give broker time to start on the standby waitForDG($stbyLDA); debug("dgcreate.addResource: $indoc"); processStatus(get_dg_document($prmyLDA, $indoc)); debug("dgcreate.addResource: Resource $resName enabled"); ## For 10i, fix up the display name if($is10i){ $indoc = ""; debug("dgcreate.addResource: $indoc"); processStatus(get_dg_document($prmyLDA, $indoc)); debug("dgcreate.addResource: Display name set to $displayName for DB object"); } } sub processStatus{ my($status) = @_; my @dbres_status_tokens = split(/[><]+/, $status); # Token 0 is "", token 1 is "RESULT ", token 2 is "MESSAGE " my($dbres_status) = $dbres_status_tokens[3]; my @temp_tokens = split(/\s+/, $dbres_status); $dbres_status = $temp_tokens[0]; EMD_PERL_DEBUG("dbres_status=$dbres_status."); if ($dbres_status eq "SUCCESS") { debug("dgcreate.processStatus: DG operation successful"); return; } else { # Parse out the error from the big status. my $dbres_status_token; my $token_count = 0; my($dbres_status_text); foreach $dbres_status_token (@dbres_status_tokens) { # EMD_PERL_DEBUG("dbres_status_token=$dbres_status_token."); if ($dbres_status_token eq "ERROR_TEXT ") { $dbres_status_text = $dbres_status_tokens[$token_count + 1]; last; } $token_count++; } if ($dbres_status eq "WARNING"){ debug("dgcreate.processStatus: DG operation produced warning: $dbres_status_text"); } elsif ($dbres_status eq "FAILURE"){ die("$dbres_status_text"); } } } ########################################################## ## Setup for standby creation ########################################################## sub createAdmin{ debug("dgcreate.createAdmin: @_"); ## globals ($Instance, $OracleHome, $listenerFile, $initFile, $cloneType, $dgOpts) = @_; ## Harvest DG options ## 1) dev mode ## 2) site ID ## 3) DB resource name my(@dgopts) = split(/:/, $dgOpts); # SJC 9/18/03: Disable development mode #$devMode = $dgopts[0]; $devMode = 0; $siteID = $dgopts[1]; $resName = $dgopts[2]; ## displayName is 10i only $displayName = $dgopts[3]; ## dbObjID is 10i only; distinct from siteID $dbObjID = $dgopts[4]; ## autoDelete is 10i only; determines if rec area uses auto deletion $autoDelete = $dgopts[5]; if($devMode){ debug("dgcreate.createAdmin: Development mode"); } elsif(-e "/tmp/dgNoNewID"){ debug("dgcreate.createAdmin: No NID mode"); $devMode = 1; } if($cloneType =~ /LOGICAL_STANDBY/){ debug("dgcreate.createAdmin: Logical Standby"); $isLogical = 1; } else{ debug("dgcreate.createAdmin: Physical Standby"); $isLogical = 0; } debug("dgcreate.createAdmin: site ID: $siteID"); debug("dgcreate.createAdmin: resource name: $resName"); ## Figure out DB Version (primary and standby will be same) $dbver = get_dbversion($prmyLDA); if($dbver =~ /^102/){ debug("dgcreate.createAdmin: Database version: 10gR2"); $is10i = 1; $dbVer = "10.2"; } elsif($dbver =~ /^101|100/){ debug("dgcreate.createAdmin: Database version: 10gR1"); $is10i = 1; $dbVer = "10.1"; } else{ debug("dgcreate.createAdmin: Database version: 9iR2"); $is10i = 0; $dbVer = "9.2"; } ## Environmentals $ENV{ORACLE_HOME} = "$OracleHome"; debug("ORACLE_HOME = $ENV{ORACLE_HOME}"); ## Since the listener.ora file may not be the default one, ## set TNS_ADMIN to whatever directory the file is in if(defined($listenerFile) && $listenerFile){ my($idx) = rindex($listenerFile,"$S"); if($idx == -1){ die("ERROR_TNS_ADMIN:$listenerFile"); return; } my($tns) = substr($listenerFile,0,$idx); $ENV{TNS_ADMIN} = "$tns"; debug("TNS_ADMIN = $ENV{TNS_ADMIN}"); } if(defined($ENV{LD_LIBRARY_PATH})){ $ENV{LD_LIBRARY_PATH} = "${OracleHome}${S}lib:$ENV{LD_LIBRARY_PATH}"; } else{ $ENV{LD_LIBRARY_PATH} = "${OracleHome}${S}lib"; } debug("LD_LIBRARY_PATH = $ENV{LD_LIBRARY_PATH}"); if(defined($ENV{PATH})){ $ENV{PATH} = "${OracleHome}${S}bin:$ENV{PATH}"; } else{ $ENV{PATH} = "${OracleHome}${S}bin"; } debug("PATH = $ENV{PATH}"); $InstanceHome = "${OracleHome}${S}dbs"; if($NT){ $InstanceHome = "${OracleHome}${S}database"; } if(defined($initFile) && $initFile){ $Ifile = $initFile; $IfileForMV = substr($Ifile, rindex($Ifile,"$S") + 1); ## Beware that the value of $Ifiletmp will be inferred as ## "${Ifile}tmp" by the client, so any changes here must be performed ## in the client code, too. $Ifiletmp1 = "${Ifile}tmp1"; $Ifiletmp2 = "${Ifile}tmp2"; } if(!(-e "$InstanceHome")){ die("ERROR_DIR_EXIST:$InstanceHome"); return; } } ########################################################## ## Move the "real" init.ora file to the correct name ########################################################## sub mvIfile{ local($SIG{'CHLD'}) = 'DEFAULT'; my($file) = $_[0]; ## default: move temp file #1 to permanent slot if(!defined($file)){ $file = $Ifiletmp1; } debug("dgcreate.mvfile: $_[0]"); my($cmd) = "$MV $file $Ifile"; if($NT){ # For NT, first delete the destination file. my($delcmd) = "del $Ifile"; debug("\t$delcmd"); &runCmd($delcmd); $cmd = "$MV $file $IfileForMV"; } debug("$cmd"); if(&runCmd($cmd)){ return(1); } } ########################################################## ## Install a new init.ora file ########################################################## sub putIfile{ debug("dgcreate.putIfile"); my($db_name) = @_; my($line); ## Create main init.ora file for standby ($Ifiletmp1). It will actually be ## created under the temporary file name, initially. open(IFILETMP1,">$Ifiletmp1") || die("ERROR_FILE_OPEN:$Ifiletmp1"); ## Create temp init.ora ($Ifiletmp2) file w/o db_file_name_convert, ## log_file_name_convert, or standby_file_management. This is ## so standby can start w/o these params set in order to do the ## file renaming. Also, strip log_archive_format parameter, so ## that if a manual recovery is done by the create process, a ## user specified format doesn't get in the way (log_archive_format ## will be preserved in the real spfile). open(IFILETMP2,">$Ifiletmp2") || die("ERROR_FILE_OPEN:$Ifiletmp2"); ## Open the file copied over by DB Clone for reading, from which ## the above two files will be created. open(IFILE,"$Ifile") || die("ERROR_FILE_OPEN:$Ifile"); while($line = ){ chomp($line); ## For Logical, substitute the new db_name, so it will match ## the name set by the NID utility if((defined($db_name)) && $line =~ /^\s*(db_name)/i){ debug("\tadding new db_name=$db_name for logical stby"); print IFILETMP1 "db_name=$db_name\n"; ## Keep old db_name in tempfile print IFILETMP2 "$line\n"; next; } ## remove lock_name_space from main file; don't need it after db name change elsif((defined($db_name)) && $line =~ /^\s*lock_name_space/i){ print IFILETMP2 "$line\n"; next; } ## remove db/log_file_name_convert from temp file if($line =~ /^\s*(db_file_name_convert|log_file_name_convert|standby_file_management)/i){ print IFILETMP1 "$line\n"; next; } print IFILETMP1 "$line\n"; print IFILETMP2 "$line\n"; } close(IFILE); close(IFILETMP1); close(IFILETMP2); ## Now that we're done producing the two init.ora files, move ## the temp one into place. ## Return with no output if error; mvIfile will output an error messages if(&mvIfile("$Ifiletmp2")){ return; } } ########################################################## ## Create a remote login passwordfile ########################################################## sub orapwd{ local($SIG{'CHLD'}) = 'DEFAULT'; debug("dgcreate.orapwd"); my($password) = $stbyPW; my($pwfile) = "${OracleHome}${S}dbs${S}orapw${Instance}"; my($orapwexec) = "${OracleHome}${S}bin${S}orapwd"; if($NT){ $pwfile = "${OracleHome}${S}database${S}PWD${Instance}.ora"; $orapwexec = "${OracleHome}${S}bin${S}orapwd.exe"; } my($pwcmd) = "$orapwexec file=$pwfile password=$password entries=10"; my($display) = "$orapwexec file=$pwfile password=(password) entries=10"; debug("$display"); ## If the executable is there, and the file isn't, create it if((-e "$orapwexec") && !(-e "$pwfile")){ if(&runCmd($pwcmd)){ exit(1); } debug("Created file $pwfile"); &printDebug("$display"); } ## else, if file is there, remove it and recreate elsif(-e "$pwfile"){ debug("Removing file $pwfile"); if(unlink($pwfile)){ if(&runCmd($pwcmd)){ exit(1); } debug("Created file $pwfile"); &printDebug("$display"); } ## If remove fails, that's an error else{ die("ERROR_RM_FILE:$pwfile"); } } ## ## else, there's a problem (probably no orapwd exec) else{ die("ERROR_EXEC:orapwd"); } } ########################################################## ## Run the NID utility to change the database name. (Called ## only to change dbname for a logical standby.) ########################################################## sub nid{ local($SIG{'CHLD'}) = 'DEFAULT'; debug("dgcreate.nid: $_[0]"); my($dbname) = @_; my($nidexec) = "${OracleHome}${S}bin${S}nid"; if($NT){ $nidexec = "${OracleHome}${S}bin${S}nid.exe"; } ## If a connect alias isn't specified, NID will connect ## to whatever ORACLE_SID is set to $ENV{ORACLE_SID} = "$Instance"; my($cmd) = "echo Y | $nidexec TARGET=/ DBNAME=$dbname"; debug("$cmd"); ## If the executable is there, and the file isn't, create it if(-e "$nidexec"){ if(&runCmd($cmd)){ exit(1); } } else{ die("ERROR_EXEC:nid"); } } ########################################################## ## Remove the SPFILE file for an instance ## ## Since this is run at create time, the globals $Instance and ## $OracleHome will be set when it's called. ########################################################## sub rmInitFiles{ debug("dgcreate.rmInitFiles"); my($file); my($file) = "${OracleHome}${S}dbs${S}spfile${Instance}.ora"; if($NT){ $file = "${OracleHome}${S}database${S}spfile${Instance}.ora"; } debug("\tlooking for $file"); if(-e "$file"){ debug("\tremoving file for for ${Instance}: $file"); unlink($file) || die("ERROR_RM_FILE:$file"); } } ########################################################## ## Setup the arch log file auto deletion policy using RMAN ## $autoDelete global set in createAdmin ########################################################## sub setAutoDelete{ debug("dgcreate.setAutoDelete"); ## Logical auto delete feature (10.2 only); always set this if($isLogical && ($dbVer eq "10.2")){ my $sql = "Begin DBMS_LOGSTDBY.APPLY_SET('LOG_AUTO_DELETE','TRUE'); End;"; printDebug("dgcreate.setAutoDelete: $sql"); my $dbcur = $stbyLDA->prepare($sql); $dbcur->execute; } if(!$is10i || ($autoDelete !~ /true/i)){ return; } $target_home = $OracleHome; $target_sid = $Instance; $db_10_or_higher = "YES"; $rman_script = "CONFIGURE ARCHIVELOG DELETION POLICY TO APPLIED ON STANDBY;"; ## Don't want fatal error here to fail the job; trap any errors my($status) = eval{rman("NO", "");}; if($@){ debug("dgcreate.setAutoDelete: RMAN script failed: $@"); } debug("dgcreate.setAutoDelete: RMAN returned $status"); } sub runCmd{ local($SIG{'CHLD'}) = 'DEFAULT'; my($cmd) = $_[0]; my(@res) = `$cmd 2>&1`; debug("@res "); if($?){ my($err) = $!; ## If there is no system error, just put out the output if($err eq undef || ($err eq "")){ $err = "@res"; ## To make multiline errors fit on one line (so it ## makes it back to the client) replace all \n with tabs. ## The client will in turn replace the tabs with newlines ## before presenting the error to the user. $err =~ s/\n/\t/g; } chomp($err); ## Send back only the OS components of error to client ## to minimize translation issues if($cmd !~ /password/i){ printDebug("${cmd}: $err"); } return(1); } return(0); } 1;