Thursday, November 13, 2025

#!/bin/bash ################################################################################ # Oracle 19c RAC Database Administration - Common Utility Functions # Description: Common utility functions used across all administration tasks # Includes integrated Data Guard common functions # Created: 2025-11-02 # Updated: 2025-11-10 - Integrated DG common functions # Updated: 2025-11-14 - DATABASE LOADING FIX APPLIED ################################################################################ ################################################################################ # CRITICAL: Global Database Arrays - MUST BE DECLARED BEFORE ANY FUNCTIONS # These arrays store loaded database configurations for menu display ################################################################################ declare -g -a DB_NAMES=() declare -g -a DB_HOSTS=() declare -g -a DB_SERVICES=() # Global variables for selected database declare -g SELECTED_DB_NAME="" declare -g SELECTED_DB_HOST="" declare -g SELECTED_DB_SERVICE="" ################################################################################ # LOGGING AND UTILITY FUNCTIONS ################################################################################ ################################################################################ # Function: log_message # Description: Logs messages with timestamp and log level # Parameters: $1 - Log Level (INFO, WARN, ERROR, DEBUG) # $2 - Message ################################################################################ log_message() { local log_level=$1 local message=$2 local timestamp=$(date '+%Y-%m-%d %H:%M:%S') local log_file="${LOG_BASE_DIR}/oracle_admin_$(date '+%Y%m%d').log" # Create log directory if it doesn't exist mkdir -p "${LOG_BASE_DIR}" # Log message echo "[${timestamp}] [${log_level}] ${message}" >> "${log_file}" # Also print to console if not silent mode if [[ "${SILENT_MODE}" != "YES" ]]; then case ${log_level} in ERROR) echo -e "\033[0;31m[${timestamp}] [${log_level}] ${message}\033[0m" ;; WARN) echo -e "\033[0;33m[${timestamp}] [${log_level}] ${message}\033[0m" ;; INFO) echo -e "\033[0;32m[${timestamp}] [${log_level}] ${message}\033[0m" ;; *) echo "[${timestamp}] [${log_level}] ${message}" ;; esac fi # Audit logging if [[ "${ENABLE_AUDIT_LOG}" == "YES" ]] && [[ "${log_level}" == "ERROR" || "${log_level}" == "WARN" ]]; then echo "[${timestamp}] [${log_level}] [${USER}] ${message}" >> "${AUDIT_LOG_FILE}" fi } ################################################################################ # Function: validate_prerequisites # Description: Validates all prerequisites before executing any task # Returns: 0 if success, 1 if failure ################################################################################ validate_prerequisites() { log_message "INFO" "Validating prerequisites..." # Check if Oracle Home is set if [[ -z "${ORACLE_HOME}" ]]; then log_message "ERROR" "ORACLE_HOME is not set" return 1 fi # Check if Oracle binaries exist if [[ ! -f "${ORACLE_HOME}/bin/sqlplus" ]]; then log_message "ERROR" "sqlplus binary not found in ${ORACLE_HOME}/bin" return 1 fi # Check if configuration file exists if [[ ! -f "${CONFIG_FILE}" ]]; then log_message "ERROR" "Configuration file not found: ${CONFIG_FILE}" return 1 fi # Check if database list file exists if [[ ! -f "${DATABASE_LIST_FILE}" ]]; then log_message "ERROR" "Database list file not found: ${DATABASE_LIST_FILE}" return 1 fi # Create required directories mkdir -p "${LOG_BASE_DIR}" "${REPORT_BASE_DIR}" "${BACKUP_DIR}" log_message "INFO" "Prerequisites validated successfully" return 0 } ################################################################################ # DATABASE CONNECTION AND VALIDATION FUNCTIONS ################################################################################ ################################################################################ # Function: load_database_list # Description: Loads database configuration from database_list.txt # Parameters: $1 - Database name (or "ALL" for all databases) # Returns: Database configuration line(s) in format: DB_NAME|SCAN|SERVICE # Empty if database not found # Exit: Exits script if database not found or validation fails ################################################################################ load_database_list() { local db_name=$1 log_message "INFO" "Loading database list for: ${db_name}" # Validate database list file exists and is readable if [[ ! -f "${DATABASE_LIST_FILE}" ]]; then log_message "ERROR" "Database list file not found: ${DATABASE_LIST_FILE}" echo "" return 1 fi if [[ ! -r "${DATABASE_LIST_FILE}" ]]; then log_message "ERROR" "Database list file is not readable: ${DATABASE_LIST_FILE}" echo "" return 1 fi # Check if file is empty if [[ ! -s "${DATABASE_LIST_FILE}" ]]; then log_message "ERROR" "Database list file is empty: ${DATABASE_LIST_FILE}" echo "" return 1 fi local result="" local found=0 # Read file line by line while IFS= read -r line || [[ -n "$line" ]]; do # Skip empty lines and comments [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue # Validate line format (should have exactly 3 fields separated by |) local field_count=$(echo "$line" | awk -F'|' '{print NF}') if [[ $field_count -ne 3 ]]; then log_message "WARN" "Invalid line format (expected 3 fields): $line" continue fi # Parse the line IFS='|' read -r db scan service <<< "$line" # Trim whitespace db=$(echo "$db" | xargs) scan=$(echo "$scan" | xargs) service=$(echo "$service" | xargs) # Validate each field is not empty if [[ -z "$db" || -z "$scan" || -z "$service" ]]; then log_message "WARN" "Line has empty fields: $line" continue fi # Check if this is the database we're looking for or if we want all if [[ "${db_name}" == "ALL" ]] || [[ "${db}" == "${db_name}" ]]; then if [[ -n "$result" ]]; then result="${result}\n${db}|${scan}|${service}" else result="${db}|${scan}|${service}" fi found=1 log_message "INFO" "Found database: ${db} -> ${scan}/${service}" # If looking for specific database, we can stop here if [[ "${db_name}" != "ALL" ]]; then break fi fi done < "${DATABASE_LIST_FILE}" # Check if we found any databases if [[ $found -eq 0 ]]; then if [[ "${db_name}" == "ALL" ]]; then log_message "ERROR" "No valid database entries found in ${DATABASE_LIST_FILE}" else log_message "ERROR" "Database '${db_name}' not found in ${DATABASE_LIST_FILE}" fi echo "" return 1 fi # Return the result echo -e "$result" return 0 } ################################################################################ # Function: load_database_list_to_arrays # Description: Load ALL databases from configuration file into global arrays # This function is for menu display and database selection # Usage: load_database_list_to_arrays # Returns: 0 on success, 1 on failure # Populates: DB_NAMES[], DB_HOSTS[], DB_SERVICES[] global arrays ################################################################################ load_database_list_to_arrays() { local db_list_file="${DATABASE_LIST_FILE}" # Clear existing arrays DB_NAMES=() DB_HOSTS=() DB_SERVICES=() # Check if file exists if [[ ! -f "$db_list_file" ]]; then log_message "ERROR" "Database list file not found: $db_list_file" echo "ERROR: Database list file not found: $db_list_file" return 1 fi # Check if file is readable if [[ ! -r "$db_list_file" ]]; then log_message "ERROR" "Database list file not readable: $db_list_file" echo "ERROR: Database list file not readable: $db_list_file" return 1 fi log_message "INFO" "Loading database list into arrays from: $db_list_file" local line_num=0 local valid_entries=0 local skipped_lines=0 # Read file line by line - preserve exact line content with IFS= while IFS= read -r line || [[ -n "$line" ]]; do ((line_num++)) # Skip empty lines [[ -z "$line" ]] && continue # Skip comment lines [[ "$line" =~ ^[[:space:]]*# ]] && continue # Parse using awk for reliable field separation local db_name=$(echo "$line" | awk -F'|' '{print $1}' | xargs) local scan_host=$(echo "$line" | awk -F'|' '{print $2}' | xargs) local service_name=$(echo "$line" | awk -F'|' '{print $3}' | xargs) # Validate that all three fields are present and non-empty if [[ -z "$db_name" || -z "$scan_host" || -z "$service_name" ]]; then log_message "WARN" "Line $line_num: Incomplete entry (missing fields) - skipping: $line" ((skipped_lines++)) continue fi # Validate field count (should be exactly 3 fields) local field_count=$(echo "$line" | awk -F'|' '{print NF}') if [[ $field_count -ne 3 ]]; then log_message "WARN" "Line $line_num: Invalid format (expected 3 fields, got $field_count) - skipping: $line" ((skipped_lines++)) continue fi # Add to global arrays DB_NAMES+=("$db_name") DB_HOSTS+=("$scan_host") DB_SERVICES+=("$service_name") ((valid_entries++)) log_message "DEBUG" "Loaded database entry $valid_entries: $db_name | $scan_host | $service_name" done < "$db_list_file" # Report results if [[ $valid_entries -eq 0 ]]; then log_message "ERROR" "No valid database entries found in $db_list_file" echo "" echo "ERROR: No valid database entries found!" echo "Please check the format of $db_list_file" echo "Expected format: DB_NAME|SCAN_HOST|SERVICE_NAME" echo "" return 1 fi log_message "INFO" "Successfully loaded $valid_entries database entries from $db_list_file" if [[ $skipped_lines -gt 0 ]]; then log_message "WARN" "Skipped $skipped_lines invalid lines" fi return 0 } ################################################################################ # Function: display_database_menu # Description: Display database selection menu from loaded arrays # Usage: display_database_menu "Menu Title" # Returns: 0 on success, 1 if no databases loaded ################################################################################ display_database_menu() { local title="${1:-Select Database}" echo "" echo "==========================================" echo "$title" echo "==========================================" # Check if databases are loaded if [[ ${#DB_NAMES[@]} -eq 0 ]]; then echo "" echo "ERROR: No databases available!" echo "Please check your database_list.txt file." echo "" return 1 fi # Display database options with connection info for i in "${!DB_NAMES[@]}"; do local menu_num=$((i + 1)) printf "%2d. %-15s (%s)\n" "$menu_num" "${DB_NAMES[$i]}" "${DB_HOSTS[$i]}" done echo "" printf "%2d. Return to Main Menu\n" 0 echo "==========================================" echo "" return 0 } ################################################################################ # Function: select_database_from_menu # Description: Prompt user to select a database from the loaded arrays # Usage: select_database_from_menu "Menu Title" # Returns: Selected database index (0-based) in $? or 255 for cancel ################################################################################ select_database_from_menu() { local title="${1:-Select Database}" # Display the menu if ! display_database_menu "$title"; then return 255 # Menu display failed fi local choice read -p "Enter your choice [0-${#DB_NAMES[@]}]: " choice # Validate input is a number if ! [[ "$choice" =~ ^[0-9]+$ ]]; then echo "" echo "ERROR: Invalid input - please enter a number" echo "" return 255 fi # Check for cancel (0) if [[ $choice -eq 0 ]]; then log_message "INFO" "User cancelled database selection" return 255 fi # Convert menu choice to 0-based array index local index=$((choice - 1)) # Validate index is within array bounds if [[ $index -lt 0 || $index -ge ${#DB_NAMES[@]} ]]; then echo "" echo "ERROR: Invalid selection" echo "Please choose a number between 1 and ${#DB_NAMES[@]}, or 0 to cancel" echo "" return 255 fi # Valid selection log_message "INFO" "User selected database index $index: ${DB_NAMES[$index]}" return $index } ################################################################################ # Function: get_database_info_by_index # Description: Get database information by index and set global variables # Usage: get_database_info_by_index # Sets: SELECTED_DB_NAME, SELECTED_DB_HOST, SELECTED_DB_SERVICE # Returns: 0 on success, 1 on failure ################################################################################ get_database_info_by_index() { local index=$1 # Validate index if [[ -z "$index" ]]; then log_message "ERROR" "get_database_info_by_index: No index provided" return 1 fi if ! [[ "$index" =~ ^[0-9]+$ ]]; then log_message "ERROR" "get_database_info_by_index: Invalid index (not a number): $index" return 1 fi if [[ $index -lt 0 || $index -ge ${#DB_NAMES[@]} ]]; then log_message "ERROR" "get_database_info_by_index: Index out of range: $index (valid: 0-$((${#DB_NAMES[@]} - 1)))" return 1 fi # Set global variables SELECTED_DB_NAME="${DB_NAMES[$index]}" SELECTED_DB_HOST="${DB_HOSTS[$index]}" SELECTED_DB_SERVICE="${DB_SERVICES[$index]}" log_message "INFO" "Selected database: $SELECTED_DB_NAME @ $SELECTED_DB_HOST ($SELECTED_DB_SERVICE)" return 0 } ################################################################################ # Function: display_selected_database_info # Description: Display information about the currently selected database # Usage: display_selected_database_info ################################################################################ display_selected_database_info() { echo "" echo "Selected Database Information:" echo " Database Name: $SELECTED_DB_NAME" echo " SCAN Host: $SELECTED_DB_HOST" echo " Service Name: $SELECTED_DB_SERVICE" echo "" } ################################################################################ # Function: test_db_connection # Description: Tests database connectivity using sqlplus # Parameters: $1 - SCAN address # $2 - Service name # Returns: 0 if connection successful, 1 if failed # Output: Prints error message on failure ################################################################################ test_db_connection() { local scan=$1 local service=$2 local connect_string="${SYS_USER}/${SYS_PASSWORD}@${scan}/${service} as sysdba" log_message "INFO" "Testing connection to ${scan}/${service}..." # Test connection with a simple query local test_result=$(${ORACLE_HOME}/bin/sqlplus -S /nolog << EOF whenever sqlerror exit 1 whenever oserror exit 1 connect ${connect_string} set heading off feedback off pagesize 0 select 'CONNECTION_SUCCESS' from dual; exit; EOF ) local exit_code=$? if [[ $exit_code -eq 0 ]] && echo "${test_result}" | grep -q "CONNECTION_SUCCESS"; then log_message "INFO" "Connection successful to ${scan}/${service}" return 0 else log_message "ERROR" "Connection failed to ${scan}/${service}" log_message "ERROR" "Error details: ${test_result}" return 1 fi } ################################################################################ # Function: validate_database_connection # Description: Validates database connection and exits if it fails # Parameters: $1 - Database name # $2 - SCAN address # $3 - Service name # Exit: Exits script with error code 1 if connection fails ################################################################################ validate_database_connection() { local db_name=$1 local scan=$2 local service=$3 log_message "INFO" "Validating connection to database: ${db_name}" if ! test_db_connection "${scan}" "${service}"; then log_message "ERROR" "Cannot connect to database ${db_name}" log_message "ERROR" "SCAN: ${scan}, Service: ${service}" log_message "ERROR" "Script execution stopped due to connection failure" echo "" echo "╔════════════════════════════════════════════════════════════════╗" echo "║ DATABASE CONNECTION VALIDATION FAILED ║" echo "╠════════════════════════════════════════════════════════════════╣" echo "║ Database: ${db_name}" echo "║ SCAN: ${scan}" echo "║ Service: ${service}" echo "║" echo "║ Possible causes:" echo "║ 1. Database is down" echo "║ 2. Listener is not running" echo "║ 3. Network connectivity issues" echo "║ 4. Invalid credentials in oracle_admin.conf" echo "║ 5. Service name is incorrect" echo "║" echo "║ Action: Fix the connection issue before retrying" echo "╚════════════════════════════════════════════════════════════════╝" echo "" exit 1 fi log_message "INFO" "Database connection validated successfully: ${db_name}" return 0 } ################################################################################ # Function: get_database_role # Description: Gets the database role (PRIMARY/PHYSICAL STANDBY) # Parameters: $1 - SCAN address # $2 - Service name # Returns: Prints database role ################################################################################ get_database_role() { local scan=$1 local service=$2 local role=$(${ORACLE_HOME}/bin/sqlplus -S /nolog << EOF connect ${SYS_USER}/${SYS_PASSWORD}@${scan}/${service} as sysdba set heading off feedback off pagesize 0 select database_role from v\$database; exit; EOF ) echo "$role" | xargs } ################################################################################ # EMAIL FUNCTIONS ################################################################################ ################################################################################ # Function: send_email_report # Description: Sends email with report content # Parameters: $1 - Subject # $2 - Report file path # $3 - Email recipients (comma-separated) ################################################################################ send_email_report() { local subject=$1 local report_file=$2 local recipients=$3 log_message "INFO" "Sending email report: ${subject}" if [[ ! -f "${report_file}" ]]; then log_message "ERROR" "Report file not found: ${report_file}" return 1 fi # Send email using sendmail or mail command if command -v sendmail &> /dev/null; then { echo "Subject: ${subject}" echo "From: ${EMAIL_FROM}" echo "To: ${recipients}" echo "Content-Type: text/html; charset=UTF-8" echo "" cat "${report_file}" } | sendmail -t log_message "INFO" "Email sent successfully to ${recipients}" elif command -v mail &> /dev/null; then cat "${report_file}" | mail -s "${subject}" -a "Content-Type: text/html" "${recipients}" log_message "INFO" "Email sent successfully to ${recipients}" else log_message "ERROR" "No email command available (sendmail or mail)" return 1 fi return 0 } ################################################################################ # FILE MANAGEMENT FUNCTIONS ################################################################################ ################################################################################ # Function: cleanup_old_files # Description: Cleans up old files based on retention policy # Parameters: $1 - Directory path # $2 - Retention days ################################################################################ cleanup_old_files() { local directory=$1 local retention_days=$2 log_message "INFO" "Cleaning up files older than ${retention_days} days in ${directory}" if [[ -d "${directory}" ]]; then find "${directory}" -type f -mtime +${retention_days} -delete log_message "INFO" "Cleanup completed" else log_message "WARN" "Directory not found: ${directory}" fi } ################################################################################ # Function: format_bytes # Description: Formats bytes to human-readable format # Parameters: $1 - Bytes value ################################################################################ format_bytes() { local bytes=$1 if [[ $bytes -lt 1024 ]]; then echo "${bytes} B" elif [[ $bytes -lt 1048576 ]]; then echo "$(echo "scale=2; $bytes / 1024" | bc) KB" elif [[ $bytes -lt 1073741824 ]]; then echo "$(echo "scale=2; $bytes / 1048576" | bc) MB" else echo "$(echo "scale=2; $bytes / 1073741824" | bc) GB" fi } ################################################################################ # DATA GUARD (DGMGRL) CORE FUNCTIONS ################################################################################ ################################################################################ # Function: get_dg_broker_configuration # Description: Gets Data Guard Broker configuration name # Parameters: $1 - Database name # Returns: DG_CONFIG_NAME, DG_CONNECT_STRING (global variables) ################################################################################ get_dg_broker_configuration() { local db_name=$1 log_message "INFO" "Checking for Data Guard Broker configuration..." local selected_config=$(load_database_list "${db_name}") if [[ -z "${selected_config}" ]]; then log_message "ERROR" "Database '${db_name}' not found in configuration" return 1 fi IFS='|' read -r db scan service <<< "${selected_config}" if ! test_db_connection "${scan}" "${service}" 2>/dev/null; then log_message "ERROR" "Cannot connect to database ${db}" return 1 fi DG_CONNECT_STRING="${SYS_USER}/${SYS_PASSWORD}@${scan}/${service}" local dg_config_output=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} show configuration; exit; EOF ) if echo "${dg_config_output}" | grep -qi "ORA-\|configuration does not exist"; then log_message "WARN" "Not part of Data Guard Broker configuration" return 1 fi DG_CONFIG_NAME=$(echo "${dg_config_output}" | grep -i "Configuration -" | sed 's/.*Configuration - \(.*\)/\1/' | xargs) if [[ -z "${DG_CONFIG_NAME}" ]]; then log_message "ERROR" "Could not determine Data Guard configuration name" return 1 fi log_message "INFO" "Found Data Guard configuration: ${DG_CONFIG_NAME}" return 0 } ################################################################################ # Function: get_primary_database_dgmgrl # Description: Gets primary database unique name from DGMGRL # Returns: PRIMARY_DB_UNIQUE_NAME (global variable) ################################################################################ get_primary_database_dgmgrl() { log_message "INFO" "Identifying primary database..." if [[ -z "${DG_CONNECT_STRING}" ]]; then log_message "ERROR" "Data Guard configuration not initialized" return 1 fi local dg_show_config=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} show configuration verbose; exit; EOF ) local primary_line=$(echo "${dg_show_config}" | grep -i "Primary database" | head -1) if [[ -z "${primary_line}" ]]; then log_message "ERROR" "Could not identify primary database" return 1 fi PRIMARY_DB_UNIQUE_NAME=$(echo "${primary_line}" | sed 's/.*Primary database is \(.*\)/\1/' | xargs) log_message "INFO" "Primary database: ${PRIMARY_DB_UNIQUE_NAME}" return 0 } ################################################################################ # Function: get_all_standby_databases_dgmgrl # Description: Gets all standby database unique names from DGMGRL # Returns: STANDBY_DBS_ARRAY (array of unique names) ################################################################################ get_all_standby_databases_dgmgrl() { log_message "INFO" "Getting all standby databases..." if [[ -z "${DG_CONNECT_STRING}" ]]; then log_message "ERROR" "Data Guard configuration not initialized" return 1 fi local dg_show_config=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} show configuration verbose; exit; EOF ) STANDBY_DBS_ARRAY=() while IFS= read -r line; do if echo "${line}" | grep -qi "Physical standby database"; then # Extract database name - use awk for first field, remove all whitespace local standby_db=$(echo "${line}" | awk '{print $1}' | xargs) standby_db=$(echo "${standby_db}" | tr -d '[:space:]') if [[ -n "${standby_db}" ]]; then STANDBY_DBS_ARRAY+=("${standby_db}") log_message "INFO" "Found standby: '${standby_db}' (length: ${#standby_db})" fi fi done <<< "${dg_show_config}" log_message "INFO" "Found ${#STANDBY_DBS_ARRAY[@]} standby database(s)" # Debug: show array contents for idx in "${!STANDBY_DBS_ARRAY[@]}"; do log_message "INFO" "STANDBY_DBS_ARRAY[${idx}] = '${STANDBY_DBS_ARRAY[$idx]}'" done return 0 } ################################################################################ # Function: get_database_connection_from_dgmgrl # Description: Gets connection info (SCAN/SERVICE) for a database from DGMGRL # Parameters: $1 - Database unique name # Returns: Prints "scan|service" or empty if not found ################################################################################ get_database_connection_from_dgmgrl() { local db_unique_name=$1 log_message "INFO" "Getting connection info for ${db_unique_name} from DGMGRL..." local db_info=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} show database ${db_unique_name}; exit; EOF ) # Extract DGConnectIdentifier from output # Format: DGConnectIdentifier = 'hostname:port/service_name' local connect_id=$(echo "${db_info}" | grep -i "DGConnectIdentifier" | head -1 | sed "s/.*DGConnectIdentifier[[:space:]]*=[[:space:]]*'\([^']*\)'.*/\1/" | xargs) if [[ -z "${connect_id}" ]]; then log_message "ERROR" "Could not extract connection identifier for ${db_unique_name}" return 1 fi # Parse connect identifier # Format can be: hostname:port/service or scan:port/service or //hostname/service local scan="" local service="" # Remove leading // if present connect_id=$(echo "${connect_id}" | sed 's|^//||') # Extract hostname/scan and service if [[ "${connect_id}" =~ ^([^/:]+)(:[0-9]+)?/(.+)$ ]]; then scan="${BASH_REMATCH[1]}" service="${BASH_REMATCH[3]}" else log_message "ERROR" "Could not parse connection identifier: ${connect_id}" return 1 fi log_message "INFO" "Connection for ${db_unique_name}: ${scan}/${service}" echo "${scan}|${service}" return 0 } ################################################################################ # Function: get_primary_and_standbys_dgmgrl # Description: Gets primary and all standbys with connection info from DGMGRL # Returns: PRIMARY_DB (db|scan|service), STANDBY_DBS_ARRAY (array of db|scan|service) ################################################################################ get_primary_and_standbys_dgmgrl() { log_message "INFO" "Identifying primary and standby databases from DGMGRL..." if [[ -z "${DG_CONNECT_STRING}" ]]; then log_message "ERROR" "Data Guard configuration not initialized" return 1 fi local dg_show_config=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} show configuration verbose; exit; EOF ) # Find primary local primary_line=$(echo "${dg_show_config}" | grep -i "Primary database" | head -1) local primary_unique_name=$(echo "${primary_line}" | sed 's/.*Primary database is \(.*\)/\1/' | xargs) if [[ -n "${primary_unique_name}" ]]; then # Get connection info from DGMGRL local primary_conn=$(get_database_connection_from_dgmgrl "${primary_unique_name}") if [[ -n "${primary_conn}" ]]; then PRIMARY_DB="${primary_unique_name}|${primary_conn}" log_message "INFO" "Primary database: ${primary_unique_name}" else log_message "ERROR" "Could not get connection info for primary ${primary_unique_name}" return 1 fi fi # Find standbys STANDBY_DBS_ARRAY=() while IFS= read -r line; do if echo "${line}" | grep -qi "Physical standby database"; then local standby_db=$(echo "${line}" | awk '{print $1}' | xargs) standby_db=$(echo "${standby_db}" | tr -d '[:space:]') if [[ -n "${standby_db}" ]]; then # Get connection info from DGMGRL local standby_conn=$(get_database_connection_from_dgmgrl "${standby_db}") if [[ -n "${standby_conn}" ]]; then STANDBY_DBS_ARRAY+=("${standby_db}|${standby_conn}") log_message "INFO" "Found standby: ${standby_db}" else log_message "WARN" "Could not get connection info for standby ${standby_db}" fi fi fi done <<< "${dg_show_config}" log_message "INFO" "Found ${#STANDBY_DBS_ARRAY[@]} standby database(s)" return 0 } ################################################################################ # DATA GUARD CONTROL FUNCTIONS ################################################################################ ################################################################################ # Function: stop_apply_on_standby # Description: Stops apply process on standby using DGMGRL # Parameters: $1 - Standby database unique name # Returns: 0 if successful, 1 if failed ################################################################################ stop_apply_on_standby() { local standby_unique_name=$1 log_message "INFO" "Stopping apply on ${standby_unique_name}..." local result=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} edit database ${standby_unique_name} set state='APPLY-OFF'; exit; EOF ) if echo "${result}" | grep -qi "succeed"; then log_message "INFO" "Apply stopped on ${standby_unique_name}" return 0 else log_message "ERROR" "Failed to stop apply on ${standby_unique_name}" echo "${result}" return 1 fi } ################################################################################ # Function: start_apply_on_standby # Description: Starts apply process on standby using DGMGRL # Parameters: $1 - Standby database unique name # Returns: 0 if successful, 1 if failed ################################################################################ start_apply_on_standby() { local standby_unique_name=$1 log_message "INFO" "Starting apply on ${standby_unique_name}..." local result=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} edit database ${standby_unique_name} set state='APPLY-ON'; exit; EOF ) if echo "${result}" | grep -qi "succeed"; then log_message "INFO" "Apply started on ${standby_unique_name}" return 0 else log_message "ERROR" "Failed to start apply on ${standby_unique_name}" echo "${result}" return 1 fi } ################################################################################ # DATA GUARD STATUS FUNCTIONS ################################################################################ ################################################################################ # Function: check_dg_status_dgmgrl # Description: Gets overall Data Guard configuration status # Returns: DG_STATUS_OUTPUT, DG_OVERALL_STATUS (global variables) ################################################################################ check_dg_status_dgmgrl() { log_message "INFO" "Checking Data Guard configuration status..." if [[ -z "${DG_CONNECT_STRING}" ]]; then log_message "ERROR" "Data Guard configuration not initialized" return 1 fi DG_STATUS_OUTPUT=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} show configuration; exit; EOF ) if echo "${DG_STATUS_OUTPUT}" | grep -qi "SUCCESS"; then DG_OVERALL_STATUS="SUCCESS" elif echo "${DG_STATUS_OUTPUT}" | grep -qi "WARNING"; then DG_OVERALL_STATUS="WARNING" elif echo "${DG_STATUS_OUTPUT}" | grep -qi "ERROR"; then DG_OVERALL_STATUS="ERROR" else DG_OVERALL_STATUS="UNKNOWN" fi log_message "INFO" "Data Guard status: ${DG_OVERALL_STATUS}" return 0 } ################################################################################ # Function: check_database_status_dgmgrl # Description: Gets detailed status for a specific database # Parameters: $1 - Database unique name # Returns: DB_STATUS_OUTPUT (global variable) ################################################################################ check_database_status_dgmgrl() { local db_unique_name=$1 log_message "INFO" "Checking status for database: ${db_unique_name}" DB_STATUS_OUTPUT=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} show database ${db_unique_name}; exit; EOF ) return 0 } ################################################################################ # Function: check_standby_lag_dgmgrl # Description: Gets transport and apply lag for a standby database # Parameters: $1 - Standby database unique name # Returns: LAG_TRANSPORT, LAG_APPLY (global variables) ################################################################################ check_standby_lag_dgmgrl() { local standby_unique_name=$1 log_message "INFO" "Checking lag for standby: ${standby_unique_name}" local lag_output=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} show database ${standby_unique_name}; exit; EOF ) # Extract transport lag LAG_TRANSPORT=$(echo "${lag_output}" | grep -i "Transport Lag:" | sed 's/.*Transport Lag:[[:space:]]*\(.*\)/\1/' | xargs) if [[ -z "${LAG_TRANSPORT}" ]]; then LAG_TRANSPORT="Unknown" fi # Extract apply lag LAG_APPLY=$(echo "${lag_output}" | grep -i "Apply Lag:" | sed 's/.*Apply Lag:[[:space:]]*\(.*\)/\1/' | xargs) if [[ -z "${LAG_APPLY}" ]]; then LAG_APPLY="Unknown" fi log_message "INFO" "Standby ${standby_unique_name} - Transport Lag: ${LAG_TRANSPORT}, Apply Lag: ${LAG_APPLY}" return 0 } ################################################################################ # Function: get_database_property_dgmgrl # Description: Gets a specific property value for a database # Parameters: $1 - Database unique name # $2 - Property name # Returns: Prints property value ################################################################################ get_database_property_dgmgrl() { local db_unique_name=$1 local property_name=$2 local db_info=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} show database ${db_unique_name}; exit; EOF ) local property_value=$(echo "${db_info}" | grep -i "${property_name}" | sed "s/.*${property_name}[[:space:]]*:[[:space:]]*\(.*\)/\1/" | xargs) echo "${property_value}" return 0 } ################################################################################ # DATA GUARD VALIDATION FUNCTIONS ################################################################################ ################################################################################ # Function: validate_dgmgrl_connection # Description: Validates DGMGRL connection is working # Returns: 0 if valid, 1 if not ################################################################################ validate_dgmgrl_connection() { log_message "INFO" "Validating DGMGRL connection..." if [[ -z "${DG_CONNECT_STRING}" ]]; then log_message "ERROR" "DG_CONNECT_STRING not set" return 1 fi local test_output=$(${ORACLE_HOME}/bin/dgmgrl -silent << EOF connect ${DG_CONNECT_STRING} show configuration; exit; EOF ) if echo "${test_output}" | grep -qi "ORA-"; then log_message "ERROR" "DGMGRL connection failed" return 1 fi log_message "INFO" "DGMGRL connection validated" return 0 } ################################################################################ # End of functions_common.sh ################################################################################