First commit
This commit is contained in:
447
oracle_clone.sh
Executable file
447
oracle_clone.sh
Executable file
@@ -0,0 +1,447 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Perform a Clone of an Oracle DB
|
||||
# v1.3 - James Pattinson - October 2025
|
||||
#
|
||||
# usage: oracle_clone.sh [options] <srcSID> <tgtHOSTNAME>
|
||||
#
|
||||
# Options:
|
||||
# -h <dbhost> Source DB hostname
|
||||
# -t <timestamp> Recovery point timestamp "YYYY-MM-DD HH:MM:SS" (uses latest if not specified)
|
||||
# -n <newsid> New database SID for the clone
|
||||
# -p <pfilepath> Custom pfile for the clone
|
||||
# -a <key,value> Advanced Cloning Options (can be used multiple times)
|
||||
# -b <pdb1,pdb2> Comma-separated list of PDBs to clone (include only these PDBs. PDB$SEED is always included)
|
||||
# -c <channels> Number of RMAN channels to use
|
||||
# -d Dry run mode - show API payload without executing
|
||||
# --refresh Refresh target database before cloning (if it exists)
|
||||
#
|
||||
# Time is passed to 'date' on THIS machine, will use local timezone
|
||||
|
||||
MYDIR="$(dirname "$(realpath "$0")")"
|
||||
# source $MYDIR/rbk_api.conf
|
||||
source $MYDIR/oracle_funcs.sh
|
||||
|
||||
# Set up cleanup trap to ensure temporary files are removed
|
||||
trap 'cleanup' EXIT INT TERM
|
||||
|
||||
usage() { echo "Usage: $0 [options] <srcSID> <tgtHOSTNAME>" 1>&2
|
||||
echo "Options:" 1>&2
|
||||
echo " -h <dbhost> Source DB hostname" 1>&2
|
||||
echo " -t <timestamp> Recovery point \"YYYY-MM-DD HH:MM:SS\"" 1>&2
|
||||
echo " -n <newsid> New database SID for clone" 1>&2
|
||||
echo " -p <pfilepath> Custom pfile for the clone" 1>&2
|
||||
echo " -a <key,value> Advanced Cloning Options (can be used multiple times)" 1>&2
|
||||
echo " -b <pdb1,pdb2> Comma-separated list of PDBs to clone (include only these PDBs)" 1>&2
|
||||
echo " -c <channels> Number of RMAN channels to use" 1>&2
|
||||
echo " -d Dry run mode" 1>&2
|
||||
echo " --refresh Refresh target database before cloning (if it exists)" 1>&2
|
||||
exit 1; }
|
||||
|
||||
declare -a config_pairs
|
||||
declare -a pdb_list
|
||||
dryrun=false
|
||||
refresh_host=false
|
||||
|
||||
num_channels=""
|
||||
|
||||
# Handle long options first and rebuild argument array
|
||||
args=()
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--refresh)
|
||||
refresh_host=true
|
||||
;;
|
||||
*)
|
||||
args+=("$arg")
|
||||
;;
|
||||
esac
|
||||
done
|
||||
set -- "${args[@]}"
|
||||
|
||||
while getopts "h:dt:n:p:a:b:c:" o; do
|
||||
case "${o}" in
|
||||
h)
|
||||
RBK_HOST=${OPTARG}
|
||||
;;
|
||||
t)
|
||||
datestring=${OPTARG}
|
||||
;;
|
||||
n)
|
||||
newsid=${OPTARG}
|
||||
;;
|
||||
p)
|
||||
custompfile=${OPTARG}
|
||||
;;
|
||||
a)
|
||||
config_pairs+=("${OPTARG}")
|
||||
;;
|
||||
b)
|
||||
IFS=',' read -ra pdb_list <<< "${OPTARG}"
|
||||
;;
|
||||
c)
|
||||
num_channels=${OPTARG}
|
||||
;;
|
||||
d)
|
||||
dryrun=true
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND-1))
|
||||
|
||||
RBK_SID=$1
|
||||
RBK_TGT=$2
|
||||
|
||||
if [ -z "${RBK_SID}" ] || [ -z "${RBK_TGT}" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
echo "Connecting to Rubrik with IP $RUBRIK_IP"
|
||||
|
||||
# API call to list Oracle DBs
|
||||
echo "Finding source database: $RBK_SID"
|
||||
if ! find_database; then
|
||||
echo "ERROR: Failed to find source database $RBK_SID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$db_id" ]; then
|
||||
echo "ERROR: Could not determine database ID for $RBK_SID"
|
||||
exit 1
|
||||
fi
|
||||
echo "Source database ID: $db_id"
|
||||
|
||||
# API call to get the host ID of the target
|
||||
echo "Finding target host: $RBK_TGT"
|
||||
ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/host?name=$RBK_TGT"
|
||||
if ! rest_api_get; then
|
||||
echo "ERROR: Failed to query target host information"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if API response exists and is valid
|
||||
if [ ! -s /tmp/rbkresponse.$$ ]; then
|
||||
echo "ERROR: Empty response when querying target host"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
total=$(cat /tmp/rbkresponse.$$ | jq -r .total 2>/dev/null)
|
||||
if [ $? -ne 0 ] || [ -z "$total" ]; then
|
||||
echo "ERROR: Invalid response format when querying target host"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$total" -ne 1 ]; then
|
||||
echo "ERROR: Target host name '$RBK_TGT' does not map to a single host (found: $total)"
|
||||
echo "Available hosts:"
|
||||
cat /tmp/rbkresponse.$$ | jq -r '.data[].name' 2>/dev/null || echo "Could not parse host list"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
target_id=$(cat /tmp/rbkresponse.$$ | jq -r '.data[0].id' 2>/dev/null)
|
||||
if [ -z "$target_id" ] || [ "$target_id" = "null" ]; then
|
||||
echo "ERROR: Could not determine target host ID"
|
||||
exit 1
|
||||
fi
|
||||
echo "Target host ID: $target_id"
|
||||
|
||||
# Refresh target database if requested and it exists
|
||||
if [ "$refresh_host" = true ]; then
|
||||
echo "=== DATABASE REFRESH REQUESTED ==="
|
||||
|
||||
# Determine the target database name (use newsid if specified, otherwise use source SID)
|
||||
if [ -n "$newsid" ]; then
|
||||
target_db_name="$newsid"
|
||||
echo "Target database name: $target_db_name (specified with -n option)"
|
||||
else
|
||||
target_db_name="$RBK_SID"
|
||||
echo "Target database name: $target_db_name (using source SID)"
|
||||
fi
|
||||
|
||||
echo "Searching for existing database '$target_db_name' on host '$RBK_TGT'..."
|
||||
|
||||
# Search for existing database on target host
|
||||
ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db?name=$target_db_name"
|
||||
if ! rest_api_get; then
|
||||
echo "WARNING: Failed to query existing databases via API"
|
||||
echo "Continuing without refresh"
|
||||
else
|
||||
if [ ! -s /tmp/rbkresponse.$$ ]; then
|
||||
echo "WARNING: Empty API response when querying existing databases"
|
||||
echo "Continuing without refresh"
|
||||
else
|
||||
# Show all databases found with this name (for informational purposes)
|
||||
db_count=$(cat /tmp/rbkresponse.$$ | jq -r --arg TARGET_NAME "$target_db_name" '[.data[] | select(.name==$TARGET_NAME)] | length' 2>/dev/null)
|
||||
echo "Found $db_count database(s) with name '$target_db_name' in Rubrik inventory"
|
||||
|
||||
if [ "$db_count" -gt 0 ]; then
|
||||
echo "Database details found:"
|
||||
cat /tmp/rbkresponse.$$ | jq -r --arg TARGET_NAME "$target_db_name" '.data[] | select(.name==$TARGET_NAME) | " - Host: \(.instances[0].hostName), Relic: \(.isRelic), ID: \(.id)"' 2>/dev/null
|
||||
fi
|
||||
|
||||
# Look for database with matching name on target host (not in relic state)
|
||||
# Handle both short hostname and FQDN matching
|
||||
echo "Filtering for databases on target host '$RBK_TGT' (non-relic)..."
|
||||
existing_db_id=$(cat /tmp/rbkresponse.$$ | jq -r --arg HOST "$RBK_TGT" --arg TARGET_NAME "$target_db_name" '
|
||||
.data[] |
|
||||
select(.name==$TARGET_NAME and (.instances[0].hostName==$HOST or (.instances[0].hostName | startswith($HOST + "."))) and .isRelic==false) |
|
||||
.id' 2>/dev/null)
|
||||
|
||||
if [ -z "$existing_db_id" ] || [ "$existing_db_id" = "null" ]; then
|
||||
echo "RESULT: No matching non-relic database found on target host"
|
||||
echo " - This is normal when cloning to a new database name or host"
|
||||
echo " - Refresh skipped, proceeding with clone operation"
|
||||
else
|
||||
echo "RESULT: Match found! Database ID: $existing_db_id"
|
||||
echo "Initiating refresh of database '$target_db_name' on host '$RBK_TGT'..."
|
||||
|
||||
# Perform database refresh
|
||||
ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db/$existing_db_id/refresh"
|
||||
if ! rest_api_post_empty; then
|
||||
echo "ERROR: Failed to refresh database via API call"
|
||||
echo "Continuing with clone operation despite refresh failure"
|
||||
else
|
||||
echo "SUCCESS: Database refresh initiated for '$target_db_name'"
|
||||
echo "Monitoring database status - waiting for it to become relic..."
|
||||
echo "Press Ctrl+C to abort and exit script"
|
||||
|
||||
# Set up signal handler for monitoring loop
|
||||
monitoring_interrupted=false
|
||||
trap 'monitoring_interrupted=true; echo ""; echo "Monitoring interrupted by user - exiting script"; cleanup; exit 130' INT
|
||||
|
||||
# Monitor the database until it becomes relic or timeout
|
||||
timeout_seconds=300 # 2 minutes
|
||||
check_interval=10 # Check every 10 seconds
|
||||
elapsed_time=0
|
||||
|
||||
while [ $elapsed_time -lt $timeout_seconds ] && [ "$monitoring_interrupted" = false ]; do
|
||||
# Check current database status
|
||||
remaining=$((timeout_seconds - elapsed_time))
|
||||
|
||||
ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db/$existing_db_id"
|
||||
if rest_api_get 2>/dev/null; then
|
||||
# Extract isRelic status
|
||||
is_relic=$(cat /tmp/rbkresponse.$$ | jq -r '.isRelic' 2>/dev/null)
|
||||
|
||||
if [ "$is_relic" = "true" ]; then
|
||||
echo "SUCCESS: Database '$target_db_name' is now in relic mode"
|
||||
echo "Safe to proceed with clone operation"
|
||||
break
|
||||
else
|
||||
echo "Status check: Database still active, waiting... (${remaining}s remaining)"
|
||||
fi
|
||||
else
|
||||
echo "WARNING: Failed to check database status, continuing anyway..."
|
||||
break
|
||||
fi
|
||||
|
||||
# Use a loop with 1-second sleeps to make Ctrl+C more responsive
|
||||
sleep_count=0
|
||||
while [ $sleep_count -lt $check_interval ] && [ "$monitoring_interrupted" = false ]; do
|
||||
sleep 1
|
||||
sleep_count=$((sleep_count + 1))
|
||||
done
|
||||
|
||||
elapsed_time=$((elapsed_time + check_interval))
|
||||
done
|
||||
|
||||
# Restore original signal handler
|
||||
trap 'cleanup' EXIT INT TERM
|
||||
|
||||
# Check if we were interrupted
|
||||
if [ "$monitoring_interrupted" = true ]; then
|
||||
exit 130
|
||||
fi
|
||||
|
||||
# Check if we timed out
|
||||
if [ $elapsed_time -ge $timeout_seconds ]; then
|
||||
echo "ERROR: Timeout after 2 minutes waiting for database to become relic"
|
||||
echo "The target database '$target_db_name' is still running on host '$RBK_TGT'"
|
||||
echo "Please shut down Oracle database '$target_db_name' before attempting clone"
|
||||
echo "Clone operation aborted to prevent conflicts"
|
||||
echo "=== SCRIPT ABORTED DUE TO TIMEOUT ==="
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo "=== REFRESH PROCESSING COMPLETE ==="
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Convert datestamp from string into milliseconds
|
||||
if [ -z "$datestring" ]; then
|
||||
echo "No timestamp specified, determining latest recoverable point"
|
||||
ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/$db_id/recoverable_range"
|
||||
if ! rest_api_get; then
|
||||
echo "ERROR: Failed to get recoverable range for database"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -s /tmp/rbkresponse.$$ ]; then
|
||||
echo "ERROR: Empty response when getting recoverable range"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
datestring=$(cat /tmp/rbkresponse.$$ | jq -r '[.data[].endTime] | max' 2>/dev/null)
|
||||
if [ $? -ne 0 ] || [ -z "$datestring" ] || [ "$datestring" = "null" ]; then
|
||||
echo "ERROR: Could not determine latest recoverable time"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo "Requested timestamp: $datestring"
|
||||
|
||||
# Validate and convert timestamp
|
||||
if ! ts=$(date -d "$datestring" +%s 2>/dev/null); then
|
||||
echo "ERROR: Invalid timestamp format: $datestring"
|
||||
echo "Expected format: YYYY-MM-DD HH:MM:SS"
|
||||
exit 1
|
||||
fi
|
||||
((millis = ts * 1000))
|
||||
echo "Recovery point (milliseconds): $millis"
|
||||
|
||||
configmap=""
|
||||
# Build configmap from -a options
|
||||
echo "Processing advanced configuration options:"
|
||||
for pair in "${config_pairs[@]}"; do
|
||||
if [[ "$pair" != *","* ]]; then
|
||||
echo "WARNING: Skipping malformed config pair (missing comma): $pair"
|
||||
continue
|
||||
fi
|
||||
|
||||
key=$(echo "$pair" | cut -d',' -f1)
|
||||
value=$(echo "$pair" | cut -d',' -f2-)
|
||||
|
||||
# Validate key is not empty
|
||||
if [ -z "$key" ]; then
|
||||
echo "WARNING: Skipping config pair with empty key: $pair"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo " $key = $value"
|
||||
if [ -n "$configmap" ]; then
|
||||
configmap="$configmap,"
|
||||
fi
|
||||
# Escape quotes in the value for JSON
|
||||
escaped_value=$(echo "$value" | sed 's/"/\\"/g')
|
||||
configmap="$configmap\"$key\":\"$escaped_value\""
|
||||
done
|
||||
|
||||
# Build the payload
|
||||
echo "Building API payload..."
|
||||
PAYLOAD="{\"recoveryPoint\":{\"timestampMs\":$millis},\"targetOracleHostOrRacId\":\"$target_id\",\"shouldRestoreFilesOnly\":false"
|
||||
|
||||
if [ -n "$newsid" ]; then
|
||||
echo " Clone DB name: $newsid"
|
||||
PAYLOAD="$PAYLOAD,\"cloneDbName\":\"$newsid\""
|
||||
fi
|
||||
|
||||
if [ -n "$custompfile" ]; then
|
||||
echo " Custom pfile path: $custompfile"
|
||||
PAYLOAD="$PAYLOAD,\"customPfilePath\":\"$custompfile\""
|
||||
fi
|
||||
|
||||
if [ -n "$num_channels" ]; then
|
||||
echo " Number of channels: $num_channels"
|
||||
# Validate num_channels is numeric
|
||||
if ! [[ "$num_channels" =~ ^[0-9]+$ ]]; then
|
||||
echo "ERROR: Number of channels must be numeric: $num_channels"
|
||||
exit 1
|
||||
fi
|
||||
PAYLOAD="$PAYLOAD,\"numChannels\":$num_channels"
|
||||
fi
|
||||
|
||||
# Add pdbsToClone array if -b specified
|
||||
if [ ${#pdb_list[@]} -gt 0 ]; then
|
||||
pdbs_json="\"PDB\$SEED\""
|
||||
for pdb in "${pdb_list[@]}"; do
|
||||
# Validate PDB name is not empty
|
||||
if [ -n "$pdb" ]; then
|
||||
pdbs_json="$pdbs_json,\"$pdb\""
|
||||
fi
|
||||
done
|
||||
echo " Including PDBs in clone: PDB\$SEED ${pdb_list[*]}"
|
||||
PAYLOAD="$PAYLOAD,\"pdbsToClone\":[$pdbs_json]"
|
||||
fi
|
||||
|
||||
# Add advanced configuration if any
|
||||
if [ -n "$configmap" ]; then
|
||||
PAYLOAD="$PAYLOAD,\"advancedRecoveryConfigMap\":{$configmap}"
|
||||
else
|
||||
PAYLOAD="$PAYLOAD,\"advancedRecoveryConfigMap\":{}"
|
||||
fi
|
||||
PAYLOAD="$PAYLOAD}"
|
||||
|
||||
ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/$db_id/export"
|
||||
|
||||
# Validate JSON payload before sending
|
||||
if ! echo "$PAYLOAD" | jq empty 2>/dev/null; then
|
||||
echo "ERROR: Invalid JSON payload generated"
|
||||
echo "Payload: $PAYLOAD"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$PAYLOAD" > /tmp/payload.$$
|
||||
|
||||
if [ "$dryrun" = true ]; then
|
||||
echo "Dry run mode - API payload that would be sent:"
|
||||
echo "$PAYLOAD" | jq .
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Initiating clone operation..."
|
||||
if ! rest_api_post_file; then
|
||||
echo "ERROR: Failed to submit clone request"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we got a valid response with a status link
|
||||
if [ ! -s /tmp/rbkresponse.$$ ]; then
|
||||
echo "ERROR: Empty response from clone request"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ENDPOINT=$(cat /tmp/rbkresponse.$$ | jq -r '.links[0].href' 2>/dev/null)
|
||||
if [ -z "$ENDPOINT" ] || [ "$ENDPOINT" = "null" ]; then
|
||||
echo "ERROR: No status link returned from clone request"
|
||||
cat /tmp/rbkresponse.$$ | jq . 2>/dev/null || cat /tmp/rbkresponse.$$
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Monitoring clone progress..."
|
||||
while true; do
|
||||
if ! rest_api_get; then
|
||||
echo "WARNING: Failed to check status, retrying..."
|
||||
sleep 10
|
||||
continue
|
||||
fi
|
||||
|
||||
status=$(cat /tmp/rbkresponse.$$ | jq -r '.status' 2>/dev/null)
|
||||
if [ -z "$status" ] || [ "$status" = "null" ]; then
|
||||
echo "WARNING: Could not determine status, retrying..."
|
||||
sleep 10
|
||||
continue
|
||||
fi
|
||||
|
||||
case "$status" in
|
||||
"SUCCEEDED")
|
||||
echo "CLONE OPERATION SUCCEEDED"
|
||||
cat /tmp/rbkresponse.$$ | jq . 2>/dev/null || cat /tmp/rbkresponse.$$
|
||||
exit 0
|
||||
;;
|
||||
"FAILED"|"CANCELED"|"UNDOING")
|
||||
echo "CLONE OPERATION FAILED WITH STATUS: $status"
|
||||
cat /tmp/rbkresponse.$$ | jq . 2>/dev/null || cat /tmp/rbkresponse.$$
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "Status: $status, checking again in 10 seconds..."
|
||||
;;
|
||||
esac
|
||||
|
||||
sleep 10
|
||||
done
|
||||
254
oracle_funcs.sh
Executable file
254
oracle_funcs.sh
Executable file
@@ -0,0 +1,254 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
#--------------------------------------------------------------------------------------------------------
|
||||
# Oracle shell script support functions
|
||||
# v0.2 - James Pattinson - August 2021
|
||||
# v0.3 - U701053 - 30.03.2022 - if database not found, try to search with the ENDPOINT datagurad
|
||||
# v0.4 - James Pattinson 25/01/23 - Adding support for Service Accounts
|
||||
#--------------------------------------------------------------------------------------------------------
|
||||
|
||||
MYDIR="$(dirname "$(realpath "$0")")"
|
||||
source $MYDIR/rbk_api.conf
|
||||
|
||||
# -- Mobi config ---------
|
||||
#RBK_ENV=$OUTI/shell/rubrik/conf/rbk_env
|
||||
# -- End Mobi config ----
|
||||
|
||||
# -- Mobi config -----------
|
||||
#if [ -f "${RBK_ENV}" ] ; then
|
||||
# source ${RBK_ENV}
|
||||
#else
|
||||
# echo "The ${RBK_ENV} file is not found ...."
|
||||
# exit_with_error
|
||||
#fi
|
||||
# -- End Mobi config -------
|
||||
|
||||
nowait=0
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
DATE=gdate
|
||||
else
|
||||
DATE=date
|
||||
fi
|
||||
|
||||
exit_with_error () {
|
||||
rm -f /tmp/rbkresponse.$$
|
||||
echo Aborting Script!
|
||||
exit 1
|
||||
}
|
||||
|
||||
# for canceled status
|
||||
exit_with_cancel () {
|
||||
rm -f /tmp/rbkresponse.$$
|
||||
echo Canceling Script!
|
||||
exit 1
|
||||
}
|
||||
|
||||
# for undoing status
|
||||
exit_with_undoing () {
|
||||
rm -f /tmp/rbkresponse.$$
|
||||
echo undoing Script!
|
||||
exit 1
|
||||
}
|
||||
|
||||
check_http_error () {
|
||||
# All good responses start with a 2
|
||||
if [ ${http_response:0:1} != "2" ]; then
|
||||
echo FATAL: HTTP error from API call: $http_response. The server responded with:
|
||||
cat /tmp/rbkresponse.$$ ; echo ; exit_with_error
|
||||
fi
|
||||
}
|
||||
|
||||
# Given RBK_SID return $db_id of matching database
|
||||
find_database () {
|
||||
|
||||
# First get IDs of all the mounted DBs for this SID
|
||||
ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/mount"
|
||||
rest_api_get
|
||||
|
||||
cat /tmp/rbkresponse.$$ | jq -r --arg SID "$RBK_SID" '.data[] | select(.mountedDatabaseId!=null and .mountedDatabaseName==$SID) | .mountedDatabaseId' > /tmp/mountedDBs.$$
|
||||
|
||||
# Now get a list of Oracle DBs
|
||||
ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db?name=$RBK_SID"
|
||||
rest_api_get
|
||||
|
||||
# If no dtaabse is found, try with dataguard
|
||||
if [ `cat /tmp/rbkresponse.$$ | grep "id\":" | wc -l` -eq 0 ]; then
|
||||
echo " DB not found, try with dataguard..."
|
||||
ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db?is_data_guard_group=true&is_relic=false&name=$RBK_SID"
|
||||
rest_api_get
|
||||
fi
|
||||
|
||||
#echo "================================="
|
||||
#cat /tmp/rbkresponse.$$ | jq -r '.data[]' | tee /tmp/titi
|
||||
#echo "================================="
|
||||
#echo "cat /tmp/rbkresponse.$$ | jq -r '.data[]'"
|
||||
|
||||
|
||||
# If no host is specified then just look for the DB with the right SID
|
||||
if [ -z $RBK_HOST ]; then
|
||||
|
||||
# get list of DB IDs in scope (sid matches and not a relic)
|
||||
myDBs=$(cat /tmp/rbkresponse.$$ | jq -r --arg SID "$RBK_SID" '.data[] | select(.name==$SID and .isRelic==false) | .id' ; cat /tmp/rbkresponse.$$ | jq -r --arg SID "$RBK_SID" '.data[] | select(.name==$SID and .isRelic==false) | .dataGuardGroupId' | sort | uniq)
|
||||
|
||||
for db in $myDBs; do
|
||||
id=$(echo $db | cut -d: -f 4)
|
||||
if grep -q $id /tmp/mountedDBs.$$; then
|
||||
continue
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Get the details for the specific DB ID
|
||||
read name db_id sla_id dg_type dg_id RBK_HOST num_instances grp_name < <(echo $(cat /tmp/rbkresponse.$$ | jq -r --arg ID "OracleDatabase:::$id" '.data[] | select(.id==$ID) | .name, .id, .effectiveSlaDomainId, .dataGuardType, .dataGuardGroupId, .instances[0].hostName, .numInstances, .dataGuardGroupName'))
|
||||
|
||||
# Host was specified
|
||||
else
|
||||
read name db_id sla_id dg_type dg_id RBK_HOST num_instances grp_name < <(echo $(cat /tmp/rbkresponse.$$ | jq -r --arg SID "$RBK_SID" --arg HOST "$RBK_HOST" '.data[] | select(.sid==$SID and .infraPath[0].name==$HOST and .isRelic==false) | .name, .id, .effectiveSlaDomainId, .dataGuardType, .dataGuardGroupId, .instances[0].hostName, .numInstances, .dataGuardGroupName'))
|
||||
fi
|
||||
|
||||
if [ "$dg_type" == "DataGuardMember" ]; then
|
||||
db_id=$dg_id
|
||||
# Now find SLA of the DG GROUP not just the DB (which will be unprotected)
|
||||
ENDPOINT="https://$RUBRIK_IP/api/v1/oracle/db/$db_id"
|
||||
rest_api_get
|
||||
sla_id=$(cat /tmp/rbkresponse.$$ | jq -r '.effectiveSlaDomainId')
|
||||
fi
|
||||
|
||||
if [ -z "$db_id" ]; then
|
||||
echo FATAL: No DB found with SID $RBK_SID on host $RBK_HOST
|
||||
if [ "${SCRIPT}" = "check_recoverable_range.sh" ] ; then
|
||||
export ret=11
|
||||
else
|
||||
exit_with_error
|
||||
fi
|
||||
else
|
||||
echo " db_id is: $db_id"
|
||||
fi
|
||||
}
|
||||
|
||||
check_get_token () {
|
||||
|
||||
if [ -z "${AUTH_TOKEN}" ]; then
|
||||
|
||||
if [[ "${ID}" =~ ^client ]]; then
|
||||
# Looks like an RSC service account
|
||||
id_string=$(echo $ID | cut -d\| -f 2)
|
||||
else
|
||||
# Not an RSC service account
|
||||
id_string=$(echo $ID | cut -d: -f 4)
|
||||
fi
|
||||
|
||||
# If there is a cached credential file, use it
|
||||
if [ -f ~/.rbksession.$id_string ]; then
|
||||
read expiration token < <(echo $(cat ~/.rbksession.$id_string))
|
||||
# If token expires within 30 min, get a new one
|
||||
if [ $($DATE +%s -d $expiration) -lt $(( $($DATE +%s) + 1800 )) ]; then
|
||||
get_token
|
||||
else
|
||||
AUTH_TOKEN=$token
|
||||
fi
|
||||
else
|
||||
get_token
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
get_token () {
|
||||
|
||||
MYENDPOINT="https://$RUBRIK_IP/api/v1/service_account/session"
|
||||
MYPAYLOAD="{\"serviceAccountId\":\"$ID\",\"secret\":\"$SECRET\"}"
|
||||
|
||||
http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $MYENDPOINT -H "accept: application/json" -H "Content-Type: application/json" -d $MYPAYLOAD)
|
||||
check_http_error
|
||||
|
||||
AUTH_TOKEN=$(cat /tmp/rbkresponse.$$ | jq -r '.token')
|
||||
SESSION=$(cat /tmp/rbkresponse.$$ | jq -r '.sessionId')
|
||||
EXPIRATION=$(cat /tmp/rbkresponse.$$ | jq -r '.expirationTime')
|
||||
echo "$EXPIRATION $AUTH_TOKEN" > ~/.rbksession.$id_string
|
||||
|
||||
}
|
||||
|
||||
# HTTP GET: Given $ENDPOINT write output to file
|
||||
rest_api_get () {
|
||||
check_get_token
|
||||
http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X GET $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN")
|
||||
check_http_error
|
||||
}
|
||||
|
||||
# HTTP POST: Given $ENDPOINT and $PAYLOAD write output to file
|
||||
rest_api_post () {
|
||||
check_get_token
|
||||
http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json" -d $PAYLOAD)
|
||||
check_http_error
|
||||
}
|
||||
|
||||
rest_api_post_file () {
|
||||
check_get_token
|
||||
http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json" --data-binary @/tmp/payload.$$)
|
||||
check_http_error
|
||||
rm -f /tmp/payload.$$
|
||||
}
|
||||
|
||||
rest_api_post_empty () {
|
||||
check_get_token
|
||||
http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X POST $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json")
|
||||
check_http_error
|
||||
}
|
||||
|
||||
rest_api_patch () {
|
||||
check_get_token
|
||||
http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X PATCH $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json" -d $PAYLOAD)
|
||||
check_http_error
|
||||
}
|
||||
|
||||
rest_api_delete () {
|
||||
check_get_token
|
||||
http_response=$(curl -s -k -o /tmp/rbkresponse.$$ -w "%{http_code}" -X DELETE $ENDPOINT -H "accept: application/json" -H "Authorization: Bearer $AUTH_TOKEN")
|
||||
check_http_error
|
||||
}
|
||||
|
||||
get_cluster_uuid () {
|
||||
ENDPOINT="https://$RUBRIK_IP/api/v1/cluster/me"
|
||||
rest_api_get
|
||||
cluster_uuid=$(cat /tmp/rbkresponse.$$ | jq -r .id)
|
||||
}
|
||||
|
||||
# Given an ENDPOINT of an async job, monitor it
|
||||
check_status () {
|
||||
if [ $nowait -ne 1 ]; then
|
||||
# Check the status in a loop
|
||||
while true; do
|
||||
rest_api_get
|
||||
status=$(cat /tmp/rbkresponse.$$ | jq -r '.status')
|
||||
if [ $status != "SUCCEEDED" ] && [ $status != "FAILED" ] && [ $status != "CANCELED" ] && [ $status != "UNDOING" ]; then
|
||||
echo Status is $status, checking in 30 seconds
|
||||
else
|
||||
if [ $status != "SUCCEEDED" ]; then
|
||||
|
||||
if [ $status = "CANCELED" ] ; then
|
||||
echo OPERATION FAILED WITH STATUS $status
|
||||
exit_with_cancel
|
||||
elif [ $status = "UNDOING" ] ; then
|
||||
echo OPERATION FAILED WITH STATUS $status
|
||||
exit_with_undoing
|
||||
else
|
||||
echo OPERATION FAILED WITH STATUS $status
|
||||
exit_with_error
|
||||
fi
|
||||
else
|
||||
echo OPERATION SUCCEEDED
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup () {
|
||||
rm -f /tmp/mountedDBs.$$
|
||||
rm -f /tmp/rbkresponse.$$
|
||||
rm -f /tmp/payload.$$
|
||||
}
|
||||
112
oracle_list_db.sh
Executable file
112
oracle_list_db.sh
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Example API call script for Die Mobiliar
|
||||
# v0.2 - James Pattinson - August 2021
|
||||
#
|
||||
# Lists the registered DBs for a given Oracle Host or RAC
|
||||
# and their assigned SLAs
|
||||
#
|
||||
# usage: oracle_list_db.sh <HOSTNAME>
|
||||
|
||||
MYDIR="$(dirname "$(realpath "$0")")"
|
||||
# source $MYDIR/rbk_api.conf
|
||||
source $MYDIR/oracle_funcs.sh
|
||||
|
||||
# Set up cleanup trap to ensure temporary files are removed
|
||||
trap 'rm -f /tmp/rbkdata.$$; cleanup' EXIT INT TERM
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $0 <dbhost>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RBK_HOST=$1
|
||||
|
||||
echo "Connecting to Rubrik with IP $RUBRIK_IP"
|
||||
|
||||
# API call to list Oracle DBs
|
||||
ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db"
|
||||
|
||||
# Check if rest_api_get function exists and call it
|
||||
if ! rest_api_get; then
|
||||
echo "ERROR: Failed to retrieve data from Rubrik API"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if the API response file exists and is not empty
|
||||
if [ ! -s /tmp/rbkresponse.$$ ]; then
|
||||
echo "ERROR: Empty or missing API response"
|
||||
cleanup
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract the data and store in a temporary file for processing
|
||||
# Simplified jq command - single pass instead of piping through jq twice
|
||||
if ! jq -r --arg HOST "$RBK_HOST" '
|
||||
.data[] |
|
||||
select(.infraPath[0].name==$HOST and .isRelic==false) |
|
||||
"\(.sid)|\(.effectiveSlaDomainName)|\(.isArchiveLogModeEnabled)|\(.dataGuardType)|\(.dataGuardGroupName)"
|
||||
' /tmp/rbkresponse.$$ > /tmp/rbkdata.$$; then
|
||||
echo "ERROR: Failed to process API response with jq"
|
||||
cleanup
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Calculate column widths and display results
|
||||
if [ -s /tmp/rbkdata.$$ ]; then
|
||||
# Initialize minimum column widths based on headers
|
||||
col1_width=3 # SID
|
||||
col2_width=3 # SLA
|
||||
col3_width=14 # ArchivelogMode
|
||||
col4_width=7 # DG Type
|
||||
col5_width=8 # DG Group
|
||||
|
||||
# Calculate actual maximum widths needed
|
||||
while IFS='|' read -r sid sla archlog dgtype dggroup || [ -n "$sid" ]; do
|
||||
# Handle null values and empty strings
|
||||
[ "$sid" = "null" ] && sid=""
|
||||
[ "$sla" = "null" ] && sla=""
|
||||
[ "$archlog" = "null" ] && archlog=""
|
||||
[ "$dgtype" = "null" ] && dgtype=""
|
||||
[ "$dggroup" = "null" ] && dggroup=""
|
||||
|
||||
# Update column widths if current data is longer
|
||||
[ ${#sid} -gt $col1_width ] && col1_width=${#sid}
|
||||
[ ${#sla} -gt $col2_width ] && col2_width=${#sla}
|
||||
[ ${#archlog} -gt $col3_width ] && col3_width=${#archlog}
|
||||
[ ${#dgtype} -gt $col4_width ] && col4_width=${#dgtype}
|
||||
[ ${#dggroup} -gt $col5_width ] && col5_width=${#dggroup}
|
||||
done < /tmp/rbkdata.$$
|
||||
|
||||
# Print headers with proper spacing
|
||||
printf "%-${col1_width}s %-${col2_width}s %-${col3_width}s %-${col4_width}s %-${col5_width}s\n" \
|
||||
"SID" "SLA" "ArchivelogMode" "DG Type" "DG Group"
|
||||
|
||||
# Print separator line
|
||||
printf "%-${col1_width}s %-${col2_width}s %-${col3_width}s %-${col4_width}s %-${col5_width}s\n" \
|
||||
"$(printf '%*s' $col1_width | tr ' ' '-')" \
|
||||
"$(printf '%*s' $col2_width | tr ' ' '-')" \
|
||||
"$(printf '%*s' $col3_width | tr ' ' '-')" \
|
||||
"$(printf '%*s' $col4_width | tr ' ' '-')" \
|
||||
"$(printf '%*s' $col5_width | tr ' ' '-')"
|
||||
|
||||
# Print data rows with proper spacing
|
||||
while IFS='|' read -r sid sla archlog dgtype dggroup || [ -n "$sid" ]; do
|
||||
# Handle null values and empty strings
|
||||
[ "$sid" = "null" ] && sid=""
|
||||
[ "$sla" = "null" ] && sla=""
|
||||
[ "$archlog" = "null" ] && archlog=""
|
||||
[ "$dgtype" = "null" ] && dgtype=""
|
||||
[ "$dggroup" = "null" ] && dggroup=""
|
||||
|
||||
printf "%-${col1_width}s %-${col2_width}s %-${col3_width}s %-${col4_width}s %-${col5_width}s\n" \
|
||||
"$sid" "$sla" "$archlog" "$dgtype" "$dggroup"
|
||||
done < /tmp/rbkdata.$$
|
||||
else
|
||||
echo "No Oracle databases found for host $RBK_HOST"
|
||||
fi
|
||||
|
||||
# Clean up temporary files
|
||||
rm -f /tmp/rbkdata.$$
|
||||
|
||||
cleanup
|
||||
5
rbk_api.conf
Normal file
5
rbk_api.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
# IP Address (or DNS name) of Rubrik CDM
|
||||
RUBRIK_IP=jp-edge-proxmox.pattinson.org
|
||||
|
||||
ID="client|673af632-150d-47e0-908e-66a6d71fe621"
|
||||
SECRET=lIHYPGMjmDLf3jflRRHtl1Oqf0YlfY7z0YNdwLG0VetfKuiVkIl_SsD4QAjAhEOb
|
||||
48
refresh_host.sh
Executable file
48
refresh_host.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Example API call script for Die Mobiliar
|
||||
# v0.2 - James Pattinson - August 2021
|
||||
#
|
||||
# usage: refresh_host.sh <HOSTNAME>
|
||||
|
||||
MYDIR="$(dirname "$(realpath "$0")")"
|
||||
# source $MYDIR/rbk_api.conf
|
||||
source $MYDIR/oracle_funcs.sh
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $0 <dbhost>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RBK_HOST=$1
|
||||
|
||||
echo Connecting to Rubrik with IP $RUBRIK_IP
|
||||
|
||||
# API call to get host details
|
||||
ENDPOINT="https://$RUBRIK_IP/api/v1/host?name=$RBK_HOST"
|
||||
rest_api_get
|
||||
|
||||
total=$(cat /tmp/rbkresponse.$$ | jq -r .total)
|
||||
if [ $total -ne 1 ]; then
|
||||
echo Host name of $RBK_HOST does not map to a single host:
|
||||
cat /tmp/rbkresponse.$$ | jq -r '.data[].name'
|
||||
exit_with_error
|
||||
fi
|
||||
|
||||
host_id=$(cat /tmp/rbkresponse.$$ | jq -r '.data[0].id')
|
||||
|
||||
if [ -z $host_id ]; then
|
||||
echo FATAL: Unable to map host name to ID
|
||||
exit_with_error
|
||||
fi
|
||||
|
||||
# API call to assign SLA
|
||||
ENDPOINT="https://$RUBRIK_IP/api/v1/host/$host_id/refresh"
|
||||
|
||||
echo Requesting host refresh for $RBK_HOST
|
||||
|
||||
rest_api_post_empty
|
||||
|
||||
echo Refreshed host details for $RBK_HOST
|
||||
|
||||
cleanup
|
||||
Reference in New Issue
Block a user