Clone script improvements
This commit is contained in:
87
rsc_clone.sh
87
rsc_clone.sh
@@ -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
|
||||||
exit_with_error
|
log_error "Unable to convert supplied timestamp to UTC time: $datestring"
|
||||||
|
exit_with_error
|
||||||
|
fi
|
||||||
|
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
|
fi
|
||||||
unixtime=$($DATE -d"$datestring" +%s)
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user