#!/bin/bash # # Perform a Live Mount of an Oracle DB # v0.2 - James Pattinson - August 2021 # # usage: oracle_mount.sh [-h ] [-d [-f] <"timestamp"> # # -d specify $ORACLE_HOME on target - needed for mount of Data Guard DBs # default is to performing a files-only mount # -l mount and create DB Live Mount # # Timestamp YYYY-MM-DD HH:MM:SS # 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 # Local commands to mount and unmount NFS volumes MOUNT="sudo /usr/local/sysadmin/utils/manage_rubrik_restore_mvs.ksh" UMOUNT="sudo /usr/local/sysadmin/utils/manage_rubrik_restore_mvs.ksh" usage() { echo "Usage: $0 <-d DBNAME> <-t \"timestamp\"> <-g tgthost || -r rachost> [-h ] [-o ] [-m [-l|-n]" 1>&2; exit 1; } filesonly=true while getopts "d:t:g:r:m:h:o:ln" o; do case "${o}" in d) RBK_SID=${OPTARG} ;; t) datestring=${OPTARG} ;; g) RBK_TGT=${OPTARG} ;; r) RAC_TGT=${OPTARG} ;; m) mountpoint=${OPTARG} ;; h) RBK_HOST=${OPTARG} ;; o) oraclehome=${OPTARG} ;; l) filesonly=false ;; n) nowait=true ;; *) usage ;; esac done shift $((OPTIND-1)) if [ -z "${RBK_SID}" ] || [ -z "${datestring}" ]; then usage fi #if [ -z "${RAC_TGT}" ] && [ -z "${RBK_TGT}" ]; then # usage #fi if [ -n "${RBK_TGT}" ] && [ -n "${RAC_TGT}" ]; then echo "ERROR: Specify either -g or -r, not both" usage fi if [ -z "${mountpoint}" ]; then mountpoint=/rbk fi echo Connecting to Rubrik with IP $RUBRIK_IP # API call to list Oracle DBs find_database mount_mv () { utctime=$($DATE -d"$datestring" +"%Y-%m-%d %H:%M:%S") if [ $? -ne 0 ]; then echo ERROR: Unable to convert supplied timestamp to UTC time exit_with_error fi echo INFO: Requested time is $datestring which is $utctime in UTC ENDPOINT="https://$RUBRIK_IP/api/internal/managed_volume/$mvData/snapshot" rest_api_get # Select snapshot of data volume with timestamp before requested time read mvDataSnap dtDataSnap < <(echo $(jq -r --arg t "$utctime" ' [($t) | strptime("%Y-%m-%d %H:%M:%S")] as $r | .data | map(select( (.date[:19] | strptime("%Y-%m-%dT%H:%M:%S")) as $d | $d <= $r[0] )) | sort_by(.date) | last | "\(.id) \(.date)"' /tmp/rbkresponse.$$)) echo Snapshot ID to mount is $mvDataSnap from $dtDataSnap # List MVs for logs volume ENDPOINT="https://$RUBRIK_IP/api/internal/managed_volume/$mvLogs/snapshot" rest_api_get # Select snapshot of logs volume with timestamp after requested time read mvLogsSnap dtLogsSnap < <(echo $(jq -r --arg t "$utctime" ' [($t) | strptime("%Y-%m-%d %H:%M:%S")] as $r | .data | map(select( (.date[:19] | strptime("%Y-%m-%dT%H:%M:%S")) as $d | $d >= $r[0] )) | sort_by(.date) | first | "\(.id) \(.date)"' /tmp/rbkresponse.$$)) echo Snapshot ID of Logs Volume to mount is $mvLogsSnap from $dtLogsSnap if [ -z "${RBK_TGT}" ]; then clients=$(hostname) echo INFO: hostname $clients is not fully qualified, adding domain name to it. Use -g flag to override client name pattern clients=$(hostname).$(domainname) else clients=${RBK_TGT} fi PAYLOAD="{\"hostPatterns\":[\"$clients\"]}" ENDPOINT="https://$RUBRIK_IP/api/internal/managed_volume/snapshot/$mvDataSnap/export" rest_api_post DATA_ENDPOINT=$(cat /tmp/rbkresponse.$$ | jq -r '.links[0].href') ENDPOINT="https://$RUBRIK_IP/api/internal/managed_volume/snapshot/$mvLogsSnap/export" rest_api_post LOGS_ENDPOINT=$(cat /tmp/rbkresponse.$$ | jq -r '.links[0].href') datajobdone=0 logsjobdone=0 while true; do if [ $datajobdone -eq 0 ]; then ENDPOINT=$DATA_ENDPOINT rest_api_get datastatus=$(cat /tmp/rbkresponse.$$ | jq -r '.status') echo "INFO: Data Mount status is $datastatus" if [[ $datastatus =~ SUCCEEDED|FAILED|CANCELED ]]; then datajobdone=1 dataExport=$(cat /tmp/rbkresponse.$$ | jq -r ' .links | .[] | select(.rel=="result") .href') fi fi if [ $logsjobdone -eq 0 ]; then ENDPOINT=$LOGS_ENDPOINT rest_api_get logsstatus=$(cat /tmp/rbkresponse.$$ | jq -r '.status') echo "INFO: Logs Mount status is $logsstatus" if [[ $logsstatus =~ SUCCEEDED|FAILED|CANCELED ]]; then logsjobdone=1 logsExport=$(cat /tmp/rbkresponse.$$ | jq -r ' .links | .[] | select(.rel=="result") .href') fi fi if [ $logsjobdone -eq 1 ] && [ $datajobdone -eq 1 ]; then echo Data Export details ENDPOINT=$dataExport rest_api_get dataMounts=$(cat /tmp/rbkresponse.$$ | jq -r '.channels[] | ( .ipAddress + ":" + .mountPoint)') ENDPOINT=$logsExport rest_api_get logMounts=$(cat /tmp/rbkresponse.$$ | jq -r '.channels[] | ( .ipAddress + ":" + .mountPoint)') for mountPath in $dataMounts $logMounts; do leaf=$(echo $mountPath | cut -d/ -f4) echo creating folder and mounting $mountPath at $mountpoint/$RBK_SID/$leaf mkdir -p $mountpoint/$RBK_SID/$leaf $MOUNT $mountPath $mountpoint/$RBK_SID/$leaf done cleanup exit else sleep 10 fi done } mount_snappable () { if [ "$dg_type" == "DataGuardMember" ]; then echo Initiating mount from source $RBK_SID \($grp_name\) else [[ $rac_name != "null" ]] && [[ -n "$rac_name" ]] && string=$rac_name || string=$RBK_HOST echo Initiating mount from source $RBK_SID on $string fi if [ -n "${RBK_TGT}" ]; then # API call to get the host ID of the target ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/host?name=$RBK_TGT" rest_api_get total=$(cat /tmp/rbkresponse.$$ | jq -r .total) if [ $total -ne 1 ]; then echo ERROR: Target host name of $RBK_TGT does not map to a single host. echo ERROR: Matching hosts are listed here. Please re-run using one of the following: cat /tmp/rbkresponse.$$ | jq -r '.data[].name' exit_with_error fi target_id=$(cat /tmp/rbkresponse.$$ | jq -r '.data[0].id') else # API call to get the host ID of the target ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/rac?name=$RAC_TGT" rest_api_get total=$(cat /tmp/rbkresponse.$$ | jq -r .total) if [ $total -ne 1 ]; then echo ERROR: Target RAC Cluster name of $RAC_TGT does not map to a single entity: echo ERROR: Matching clusters are listed here. Please re-run using one of the following: cat /tmp/rbkresponse.$$ | jq -r '.data[].name' exit_with_error fi target_id=$(cat /tmp/rbkresponse.$$ | jq -r '.data[0].id') fi # convert datestamp from string into milliseconds echo requested timestamp is $datestring ts=$(date -d"$datestring" +%s) if [ $? -ne 0 ]; then echo Problem with timestamp exit_with_error fi ((millis = $ts * 1000)) if [ ! -z ${oraclehome+x} ]; then configmap="\"ORACLE_HOME\":\"$oraclehome\"" fi # API call to perform the mount PAYLOAD="{\"recoveryPoint\":{\"timestampMs\":$millis},\"targetOracleHostOrRacId\":\"$target_id\",\"targetMountPath\":\"$mountpoint\",\"shouldMountFilesOnly\":$filesonly,\"advancedRecoveryConfigMap\":{$configmap}}" ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/$db_id/mount" rest_api_post ENDPOINT=$(cat /tmp/rbkresponse.$$ | jq -r '.links[0].href') LOOP=0 while true; do rest_api_get status=$(cat /tmp/rbkresponse.$$ | jq -r '.status') if [ $status != "SUCCEEDED" ] && [ $status != "FAILED" ]; then echo Status is $status, checking in 15 seconds if [ $status = "RUNNING" ] && [ ${LOOP} -ne 1 ] ; then LOOP=1 sleep 5 save=$ENDPOINT ENDPOINT="https://$RUBRIK_IP/api/internal/oracle/db/mount?source_database_name=$dbname" rest_api_get RBK_ID_LV=$(cat /tmp/rbkresponse.$$ | jq -r '.data[] | select(.status=="Mounting") | .id') ENDPOINT=$save fi else if [ $status != "SUCCEEDED" ]; then echo LIVE MOUNT FAILED WITH STATUS $status exit_with_error else echo LIVE MOUNT SUCCEEDED echo "The live mount id is: ${RBK_ID_LV}" # cat /tmp/rbkresponse.$$ | jq -r ' ' cleanup exit 0 fi fi sleep 15 done } if [ "$db_id" == "ManagedVolume" ]; then mount_mv else mount_snappable fi cleanup