Clone script improvements

This commit is contained in:
2025-10-15 17:54:20 +01:00
parent 14b74e8ecd
commit aa07962766

View File

@@ -3,9 +3,9 @@
# Example RSC API call script # Example RSC API call script
# v0.1 - James Pattinson - August 2025 # v0.1 - James Pattinson - August 2025
# #
# Perfoms a database clone operation # Performs a database clone operation
# #
# usage: rsc_clone.sh -n <newname> -o <optionsfile> -h <targethost> [-s sourcehost] [-t "YYYY-MM-DD HH:MM:SS"] [-d] [-c numChannels] <srcdb> # usage: rsc_clone.sh -n <newname> -o <optionsfile> -h <targethost> [-s sourcehost] [-t "YYYY-MM-DD HH:MM:SS"] [-d] [-c numChannels] [-p customPfilePath] <srcdb>
# #
# Options: # Options:
# -n <newname> : db_name / SID of the new cloned database # -n <newname> : db_name / SID of the new cloned database
@@ -15,6 +15,7 @@
# -t "YYYY-MM-DD HH:MM:SS" : Optional timestamp for the recovery point, defaults to latest PIT # -t "YYYY-MM-DD HH:MM:SS" : Optional timestamp for the recovery point, defaults to latest PIT
# -d : Dry-run mode - show mutation variables without executing the clone # -d : Dry-run mode - show mutation variables without executing the clone
# -c <numChannels> : Optional - number of RMAN channels to configure for the clone # -c <numChannels> : Optional - number of RMAN channels to configure for the clone
# -p <customPfilePath> : Optional - custom pfile path for the clone
# <srcdb> : Source database name or RSC dbid (if known, can be used directly) # <srcdb> : Source database name or RSC dbid (if known, can be used directly)
# #
# Example options file content: # Example options file content:
@@ -24,13 +25,13 @@
# DB_CREATE_FILE_DEST=/u01/app/oracle/oradata/NEWNAME/ # DB_CREATE_FILE_DEST=/u01/app/oracle/oradata/NEWNAME/
# AUDIT_FILE_DEST='/u01/app/oracle/admin/NEWNAME/adump' # AUDIT_FILE_DEST='/u01/app/oracle/admin/NEWNAME/adump'
usage() { log_error "Usage: $0 -n <newname> -o <optionsfile> -h <targethost> [-s sourcehost] [-t \"YYYY-MM-DD HH:MM:SS\"] [-d] [-c numChannels] <srcdb>"; exit 1; } usage() { log_error "Usage: $0 -n <newname> -o <optionsfile> -h <targethost> [-s sourcehost] [-t \"YYYY-MM-DD HH:MM:SS\"] [-d] [-c numChannels] [-p customPfilePath] <srcdb>"; exit 1; }
MYDIR="$(dirname "$(realpath "$0")")" MYDIR="$(dirname "$(realpath "$0")")"
source $MYDIR/rsc_ops.sh source $MYDIR/rsc_ops.sh
while getopts "n:o:t:h:s:d:v:c:" o; do while getopts "n:o:t:h:s:dc:p:" o; do
case "${o}" in case "${o}" in
n) n)
newName=${OPTARG} newName=${OPTARG}
@@ -58,6 +59,14 @@ while getopts "n:o:t:h:s:d:v:c:" o; do
exit_with_error exit_with_error
fi fi
;; ;;
p)
customPfilePath=${OPTARG}
# Basic validation - ensure it's an absolute path
if [[ ! "$customPfilePath" =~ ^/ ]]; then
log_error "-p requires an absolute path (starting with /)"
exit_with_error
fi
;;
*) *)
usage usage
;; ;;
@@ -147,7 +156,8 @@ get_latest_pit() {
rsc_gql_query rsc_gql_query
# Get latest endTime # Get latest endTime
latest_endtime=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleRecoverableRanges.data[] | .endTime' | sort -r | head -n 1) response_content=$(cat /tmp/rbkresponse.$$)
latest_endtime=$(echo "$response_content" | jq -r '.data.oracleRecoverableRanges.data[] | .endTime' | sort -r | head -n 1)
log_info "Latest PIT (ISO8601): $latest_endtime" log_info "Latest PIT (ISO8601): $latest_endtime"
# Convert to unixtime in milliseconds # Convert to unixtime in milliseconds
@@ -194,11 +204,12 @@ get_oracle_host_id() {
rsc_gql_query rsc_gql_query
# Get all matching host IDs (portable, no mapfile) # Get all matching host IDs (portable, no mapfile)
host_ids=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleTopLevelDescendants.nodes[] | .id') response_content=$(cat /tmp/rbkresponse.$$)
host_ids=$(echo "$response_content" | jq -r '.data.oracleTopLevelDescendants.nodes[] | .id')
host_count=$(echo "$host_ids" | grep -c .) host_count=$(echo "$host_ids" | grep -c .)
if [[ $host_count -ne 1 ]]; then if [[ $host_count -ne 1 ]]; then
log_error "Multiple hosts found for '$1':" log_error "Multiple hosts found for '$1':"
cat /tmp/rbkresponse.$$ | jq -r '.data.oracleTopLevelDescendants.nodes[] | "\(.name) \(.id)"' echo "$response_content" | jq -r '.data.oracleTopLevelDescendants.nodes[] | "\(.name) \(.id)"'
exit_with_error exit_with_error
fi fi
# Set the first match (or empty if none) # Set the first match (or empty if none)
@@ -224,7 +235,8 @@ if [[ "$1" == *-* ]]; then
gqlVars="$(echo $variables)" gqlVars="$(echo $variables)"
rsc_gql_query rsc_gql_query
cdmId=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabase.cluster.id') response_content=$(cat /tmp/rbkresponse.$$)
cdmId=$(echo "$response_content" | jq -r '.data.oracleDatabase.cluster.id')
if [[ -z "$cdmId" ]]; then if [[ -z "$cdmId" ]]; then
log_error "Could not find CDM ID for dbid '$dbid'" log_error "Could not find CDM ID for dbid '$dbid'"
exit 1 exit 1
@@ -270,27 +282,29 @@ oracleDatabases(filter: $filter) {
gqlVars="$(echo $variables)" gqlVars="$(echo $variables)"
rsc_gql_query rsc_gql_query
response_content=$(cat /tmp/rbkresponse.$$)
if [[ -n "$node_name" ]]; then if [[ -n "$node_name" ]]; then
# Filter by source host if specified # Filter by source host if specified
dbid=$(cat /tmp/rbkresponse.$$ | jq -r --arg NODE "$node_name" '.data.oracleDatabases.nodes[] | select(.logicalPath[]?.name | test("^" + $NODE + "(\\.|$)")) | .id') dbid=$(echo "$response_content" | jq -r --arg NODE "$node_name" '.data.oracleDatabases.nodes[] | select(.logicalPath[]?.name | test("^" + $NODE + "(\\.|$)")) | .id')
cdmId=$(cat /tmp/rbkresponse.$$ | jq -r --arg NODE "$node_name" '.data.oracleDatabases.nodes[] | select(.logicalPath[]?.name | test("^" + $NODE + "(\\.|$)")) | .cluster.id') cdmId=$(echo "$response_content" | jq -r --arg NODE "$node_name" '.data.oracleDatabases.nodes[] | select(.logicalPath[]?.name | test("^" + $NODE + "(\\.|$)")) | .cluster.id')
dbid_count=$(echo "$dbid" | grep -c .) dbid_count=$(echo "$dbid" | grep -c .)
if [[ "$dbid_count" -ne 1 || -z "$dbid" ]]; then if [[ "$dbid_count" -ne 1 || -z "$dbid" ]]; then
log_error "Expected exactly one database running on node '$node_name', found $dbid_count:" log_error "Expected exactly one database running on node '$node_name', found $dbid_count:"
cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabases.nodes[] | "\(.dbUniqueName) \(.logicalPath[0].name) \(.id)"' echo "$response_content" | jq -r '.data.oracleDatabases.nodes[] | "\(.dbUniqueName) \(.logicalPath[0].name) \(.id)"'
cleanup cleanup
exit 4 exit 4
fi fi
else else
# No source host specified, get all matching databases # No source host specified, get all matching databases
dbid=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabases.nodes[].id') dbid=$(echo "$response_content" | jq -r '.data.oracleDatabases.nodes[].id')
cdmId=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabases.nodes[].cluster.id') cdmId=$(echo "$response_content" | jq -r '.data.oracleDatabases.nodes[].cluster.id')
dbid_count=$(echo "$dbid" | grep -c .) dbid_count=$(echo "$dbid" | grep -c .)
if [[ "$dbid_count" -ne 1 || -z "$dbid" ]]; then if [[ "$dbid_count" -ne 1 || -z "$dbid" ]]; then
log_error "Expected exactly one database with name '$1', found $dbid_count:" log_error "Expected exactly one database with name '$1', found $dbid_count:"
cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabases.nodes[] | "\(.dbUniqueName) \(.logicalPath[0].name) \(.id)"' echo "$response_content" | jq -r '.data.oracleDatabases.nodes[] | "\(.dbUniqueName) \(.logicalPath[0].name) \(.id)"'
cleanup cleanup
exit 4 exit 4
fi fi
@@ -301,14 +315,20 @@ fi
# Only run UTC conversion if -t was used # Only run UTC conversion if -t was used
if [[ -n "${datestring:-}" ]]; then if [[ -n "${datestring:-}" ]]; then
utctime=$($DATE -d"$datestring" +"%Y-%m-%d %H:%M:%S") # Try date first, then gdate for macOS compatibility
if [ $? -ne 0 ]; then if ! utctime=$(date -d"$datestring" +"%Y-%m-%d %H:%M:%S" 2>/dev/null); then
echo ERROR: Unable to convert supplied timestamp to UTC time if ! utctime=$(gdate -d"$datestring" +"%Y-%m-%d %H:%M:%S" 2>/dev/null); then
log_error "Unable to convert supplied timestamp to UTC time: $datestring"
exit_with_error exit_with_error
fi fi
unixtime=$($DATE -d"$datestring" +%s) fi
# Get unixtime using the same date command that worked
if ! unixtime=$(date -d"$datestring" +%s 2>/dev/null); then
unixtime=$(gdate -d"$datestring" +%s 2>/dev/null)
fi
unixtime_ms=$((unixtime * 1000)) unixtime_ms=$((unixtime * 1000))
echo INFO: Requested time is $datestring which is $utctime in UTC, unixtime is $unixtime log_info "Requested time is $datestring which is $utctime in UTC, unixtime is $unixtime"
else else
log_info "No time specified, using latest PIT" log_info "No time specified, using latest PIT"
get_latest_pit get_latest_pit
@@ -334,6 +354,13 @@ else
numChannelsPart="" numChannelsPart=""
fi fi
# Optionally include customPfilePath in the config
if [[ -n "${customPfilePath:-}" ]]; then
customPfilePathPart="\"customPfilePath\": \"$customPfilePath\","
else
customPfilePathPart=""
fi
variables=" variables="
{ {
\"input\": { \"input\": {
@@ -341,6 +368,7 @@ variables="
\"id\": \"$dbid\", \"id\": \"$dbid\",
\"config\": { \"config\": {
$numChannelsPart $numChannelsPart
$customPfilePathPart
\"targetOracleHostOrRacId\": \"$targetHostId\", \"targetOracleHostOrRacId\": \"$targetHostId\",
\"shouldRestoreFilesOnly\": false, \"shouldRestoreFilesOnly\": false,
\"recoveryPoint\": { \"recoveryPoint\": {
@@ -386,11 +414,11 @@ if [ "$dryrun" = true ]; then
fi fi
rsc_gql_query rsc_gql_query
cat /tmp/rbkresponse.$$ | jq response_content=$(cat /tmp/rbkresponse.$$)
echo "$response_content" | jq
# Save the id from the response # Save the id from the response
job_id=$(cat /tmp/rbkresponse.$$ | jq -r '.data.exportOracleDatabase.id') job_id=$(echo "$response_content" | jq -r '.data.exportOracleDatabase.id')
log_info "Job id is $job_id" log_info "Job id is $job_id"
gqlCheckStatus='query OracleDatabaseAsyncRequestDetails($input: GetOracleAsyncRequestStatusInput!) { gqlCheckStatus='query OracleDatabaseAsyncRequestDetails($input: GetOracleAsyncRequestStatusInput!) {
@@ -419,26 +447,27 @@ gqlVars="$(echo $variables)"
while true; do while true; do
rsc_gql_query rsc_gql_query
status=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabaseAsyncRequestDetails.status') response_content=$(cat /tmp/rbkresponse.$$)
progress=$(cat /tmp/rbkresponse.$$ | jq -r '.data.oracleDatabaseAsyncRequestDetails.progress') status=$(echo "$response_content" | jq -r '.data.oracleDatabaseAsyncRequestDetails.status')
progress=$(echo "$response_content" | jq -r '.data.oracleDatabaseAsyncRequestDetails.progress')
log_info "Job status: $status $progress percent" log_info "Job status: $status $progress percent"
if [[ "$status" == "FAILED" ]]; then if [[ "$status" == "FAILED" ]]; then
log_error "Database clone FAILED" log_error "Database clone FAILED"
cat /tmp/rbkresponse.$$ | jq echo "$response_content" | jq
cleanup cleanup
exit 2 exit 2
elif [[ "$status" == "CANCELLED" ]]; then elif [[ "$status" == "CANCELLED" ]]; then
log_warn "Database clone CANCELLED" log_warn "Database clone CANCELLED"
cleanup
exit 3 exit 3
elif [[ "$status" == "SUCCEEDED" ]]; then elif [[ "$status" == "SUCCEEDED" ]]; then
log_info "Database clone SUCCEEDED" log_info "Database clone SUCCEEDED"
cat /tmp/rbkresponse.$$ | jq echo "$response_content" | jq
cleanup cleanup
exit 0 exit 0
fi fi
sleep 15 sleep 15
done done