Friday, November 14, 2025

#!/bin/bash ################################################################################ # Oracle 19c RAC Database Administration - Main Script # Description: Main menu system for Oracle RAC administration # Version: 2.1 - Complete Fix Applied # Created: 2025-11-02 # Updated: 2025-11-14 - COMPREHENSIVE FIX APPLIED ################################################################################ ################################################################################ # SCRIPT INITIALIZATION ################################################################################ # Get script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_NAME=$(basename "$0") # Set configuration file paths CONFIG_FILE="${SCRIPT_DIR}/config/script_config.conf" DATABASE_LIST_FILE="${SCRIPT_DIR}/config/database_list.txt" # Set directory paths LOG_BASE_DIR="${SCRIPT_DIR}/logs" REPORT_BASE_DIR="${SCRIPT_DIR}/reports" BACKUP_DIR="${SCRIPT_DIR}/backups" # Create directories if they don't exist mkdir -p "${LOG_BASE_DIR}" "${REPORT_BASE_DIR}" "${BACKUP_DIR}" "${SCRIPT_DIR}/config" # Source common functions library if [[ -f "${SCRIPT_DIR}/functions_common.sh" ]]; then source "${SCRIPT_DIR}/functions_common.sh" else echo "ERROR: functions_common.sh not found in ${SCRIPT_DIR}" echo "Please ensure the common functions library exists" exit 1 fi # Source function modules for func_file in "${SCRIPT_DIR}"/functions_*.sh; do if [[ -f "$func_file" ]] && [[ "$func_file" != *"functions_common.sh" ]]; then source "$func_file" echo "Loaded: $(basename "$func_file")" fi done ################################################################################ # INITIALIZATION - LOAD DATABASE LIST INTO ARRAYS ################################################################################ # Load all databases into global arrays for menu display echo "" echo "Initializing Oracle RAC Administration System..." echo "Loading database configuration..." echo "" if ! load_database_list_to_arrays; then echo "" echo "===============================================" echo "CRITICAL ERROR: Failed to load database list!" echo "===============================================" echo "" echo "Please check:" echo " 1. File exists: ${DATABASE_LIST_FILE}" echo " 2. File is readable" echo " 3. File has valid entries in format: DB_NAME|SCAN_HOST|SERVICE_NAME" echo "" echo "Example format:" echo " PRODDB|prod-scan.company.com|PRODDB_SVC" echo " TESTDB|test-scan.company.com|TESTDB_SVC" echo "" exit 1 fi echo "✓ Successfully loaded ${#DB_NAMES[@]} database(s)" echo "" ################################################################################ # MAIN MENU DISPLAY ################################################################################ display_main_menu() { clear echo "==========================================" echo "Oracle 19c RAC Administration System" echo "Version 2.1 - Complete Fix Applied" echo "==========================================" echo "" echo "Available Databases:" # Display loaded databases from arrays if [[ ${#DB_NAMES[@]} -eq 0 ]]; then echo " WARNING: No databases loaded!" echo " Please check ${DATABASE_LIST_FILE}" else for i in "${!DB_NAMES[@]}"; do printf " %2d. %-15s @ %-30s [%s]\n" "$((i+1))" "${DB_NAMES[$i]}" "${DB_HOSTS[$i]}" "${DB_SERVICES[$i]}" done fi echo "" echo "==========================================" echo "Main Menu Options" echo "==========================================" echo " 1. Database Health Check" echo " 2. Data Guard Status" echo " 3. Data Guard Switchover" echo " 4. Restore Point Management" echo " 5. View All Databases" echo " 6. Reload Database List" echo " 7. View Logs" echo " 0. Exit" echo "==========================================" echo "" } ################################################################################ # MENU OPTION HANDLERS ################################################################################ ################################################################################ # Option 1: Database Health Check ################################################################################ handle_database_health_check() { clear echo "==========================================" echo "Database Health Check" echo "==========================================" echo "" # Select database from menu select_database_from_menu "Select Database for Health Check" local db_index=$? # Check if user cancelled (returns 255) if [[ $db_index -eq 255 ]]; then echo "Operation cancelled." read -p "Press Enter to continue..." return fi # Get database info by index if ! get_database_info_by_index "$db_index"; then echo "ERROR: Failed to get database information" read -p "Press Enter to continue..." return fi # Display selected database info display_selected_database_info # Confirm operation read -p "Proceed with health check? (yes/no): " confirm if [[ ! "$confirm" =~ ^[Yy][Ee]?[Ss]?$ ]]; then echo "Operation cancelled." read -p "Press Enter to continue..." return fi # Source the health check functions if not already loaded if [[ -f "${SCRIPT_DIR}/functions_db_health.sh" ]]; then source "${SCRIPT_DIR}/functions_db_health.sh" else echo "ERROR: functions_db_health.sh not found" read -p "Press Enter to continue..." return fi # Perform health check echo "" echo "Performing health check..." echo "" if perform_db_health_check "$SELECTED_DB_NAME" "$SELECTED_DB_HOST" "$SELECTED_DB_SERVICE"; then echo "" echo "✓ Health check completed successfully" else echo "" echo "✗ Health check failed or completed with errors" fi echo "" read -p "Press Enter to continue..." } ################################################################################ # Option 2: Data Guard Status ################################################################################ handle_data_guard_status() { clear echo "==========================================" echo "Data Guard Status Check" echo "==========================================" echo "" # Select database from menu select_database_from_menu "Select Database for Data Guard Status" local db_index=$? # Check if user cancelled if [[ $db_index -eq 255 ]]; then echo "Operation cancelled." read -p "Press Enter to continue..." return fi # Get database info if ! get_database_info_by_index "$db_index"; then echo "ERROR: Failed to get database information" read -p "Press Enter to continue..." return fi # Display selected database info display_selected_database_info # Source DG health functions if not already loaded if [[ -f "${SCRIPT_DIR}/functions_dg_health.sh" ]]; then source "${SCRIPT_DIR}/functions_dg_health.sh" else echo "ERROR: functions_dg_health.sh not found" read -p "Press Enter to continue..." return fi # Perform Data Guard status check echo "" echo "Checking Data Guard status..." echo "" if perform_dg_health_check "$SELECTED_DB_NAME" "$SELECTED_DB_HOST" "$SELECTED_DB_SERVICE"; then echo "" echo "✓ Data Guard status check completed" else echo "" echo "✗ Data Guard status check failed" fi echo "" read -p "Press Enter to continue..." } ################################################################################ # Option 3: Data Guard Switchover ################################################################################ handle_data_guard_switchover() { clear echo "==========================================" echo "Data Guard Switchover" echo "==========================================" echo "" echo "WARNING: This operation will switch database roles" echo " Primary will become Standby and vice versa" echo "" # Select primary database echo "Step 1: Select CURRENT PRIMARY database" echo "" select_database_from_menu "Select Current Primary Database" local primary_index=$? if [[ $primary_index -eq 255 ]]; then echo "Operation cancelled." read -p "Press Enter to continue..." return fi # Get primary database info if ! get_database_info_by_index "$primary_index"; then echo "ERROR: Failed to get primary database information" read -p "Press Enter to continue..." return fi local primary_db="$SELECTED_DB_NAME" local primary_host="$SELECTED_DB_HOST" local primary_service="$SELECTED_DB_SERVICE" echo "" echo "Primary Database Selected:" echo " Database: $primary_db" echo " Host: $primary_host" echo " Service: $primary_service" echo "" # Select standby database echo "Step 2: Select TARGET STANDBY database" echo "" select_database_from_menu "Select Target Standby Database" local standby_index=$? if [[ $standby_index -eq 255 ]]; then echo "Operation cancelled." read -p "Press Enter to continue..." return fi # Validate not selecting the same database if [[ $standby_index -eq $primary_index ]]; then echo "" echo "ERROR: Primary and Standby cannot be the same database!" read -p "Press Enter to continue..." return fi # Get standby database info if ! get_database_info_by_index "$standby_index"; then echo "ERROR: Failed to get standby database information" read -p "Press Enter to continue..." return fi local standby_db="$SELECTED_DB_NAME" local standby_host="$SELECTED_DB_HOST" local standby_service="$SELECTED_DB_SERVICE" echo "" echo "Standby Database Selected:" echo " Database: $standby_db" echo " Host: $standby_host" echo " Service: $standby_service" echo "" # Display switchover plan echo "==========================================" echo "SWITCHOVER PLAN" echo "==========================================" echo "Current Primary: $primary_db @ $primary_host" echo "Current Standby: $standby_db @ $standby_host" echo "" echo "After Switchover:" echo " New Primary: $standby_db @ $standby_host" echo " New Standby: $primary_db @ $primary_host" echo "==========================================" echo "" # Final confirmation read -p "Type 'SWITCHOVER' to confirm this operation: " confirm if [[ "$confirm" != "SWITCHOVER" ]]; then echo "Operation cancelled." read -p "Press Enter to continue..." return fi # Source switchover functions if [[ -f "${SCRIPT_DIR}/functions_dg_switchover.sh" ]]; then source "${SCRIPT_DIR}/functions_dg_switchover.sh" else echo "ERROR: functions_dg_switchover.sh not found" read -p "Press Enter to continue..." return fi # Perform switchover echo "" echo "Initiating Data Guard switchover..." echo "" if perform_dg_switchover "$primary_db" "$primary_host" "$primary_service" "$standby_db" "$standby_host" "$standby_service"; then echo "" echo "✓ Switchover completed successfully" else echo "" echo "✗ Switchover failed or rolled back" fi echo "" read -p "Press Enter to continue..." } ################################################################################ # Option 4: Restore Point Management ################################################################################ handle_restore_point_management() { clear echo "==========================================" echo "Restore Point Management" echo "==========================================" echo "" # Select database select_database_from_menu "Select Database for Restore Point Management" local db_index=$? if [[ $db_index -eq 255 ]]; then echo "Operation cancelled." read -p "Press Enter to continue..." return fi # Get database info if ! get_database_info_by_index "$db_index"; then echo "ERROR: Failed to get database information" read -p "Press Enter to continue..." return fi # Display selected database info display_selected_database_info # Source restore point functions if [[ -f "${SCRIPT_DIR}/functions_restore_point.sh" ]]; then source "${SCRIPT_DIR}/functions_restore_point.sh" else echo "ERROR: functions_restore_point.sh not found" read -p "Press Enter to continue..." return fi # Call the restore point menu manage_restore_points "$SELECTED_DB_NAME" "$SELECTED_DB_HOST" "$SELECTED_DB_SERVICE" echo "" read -p "Press Enter to continue..." } ################################################################################ # Option 5: View All Databases ################################################################################ handle_view_all_databases() { clear echo "==========================================" echo "All Configured Databases" echo "==========================================" echo "" if [[ ${#DB_NAMES[@]} -eq 0 ]]; then echo "No databases configured." echo "Please add databases to: ${DATABASE_LIST_FILE}" else printf "%-4s %-20s %-35s %-25s\n" "No." "Database Name" "SCAN Host" "Service Name" echo "--------------------------------------------------------------------------------------------" for i in "${!DB_NAMES[@]}"; do printf "%-4d %-20s %-35s %-25s\n" "$((i+1))" "${DB_NAMES[$i]}" "${DB_HOSTS[$i]}" "${DB_SERVICES[$i]}" done fi echo "" echo "Total: ${#DB_NAMES[@]} database(s)" echo "" echo "Configuration file: ${DATABASE_LIST_FILE}" echo "" read -p "Press Enter to continue..." } ################################################################################ # Option 6: Reload Database List ################################################################################ handle_reload_database_list() { clear echo "==========================================" echo "Reload Database List" echo "==========================================" echo "" echo "Current databases loaded: ${#DB_NAMES[@]}" echo "" read -p "Reload database list from configuration file? (yes/no): " confirm if [[ ! "$confirm" =~ ^[Yy][Ee]?[Ss]?$ ]]; then echo "Operation cancelled." read -p "Press Enter to continue..." return fi echo "" echo "Reloading database list..." echo "" if load_database_list_to_arrays; then echo "" echo "✓ Successfully reloaded ${#DB_NAMES[@]} database(s)" # Display reloaded databases echo "" echo "Reloaded Databases:" for i in "${!DB_NAMES[@]}"; do printf " %2d. %-15s @ %-30s [%s]\n" "$((i+1))" "${DB_NAMES[$i]}" "${DB_HOSTS[$i]}" "${DB_SERVICES[$i]}" done else echo "" echo "✗ Failed to reload database list" echo "Please check ${DATABASE_LIST_FILE}" fi echo "" read -p "Press Enter to continue..." } ################################################################################ # Option 7: View Logs ################################################################################ handle_view_logs() { clear echo "==========================================" echo "View Logs" echo "==========================================" echo "" # Find today's log file local today_log="${LOG_BASE_DIR}/oracle_admin_$(date '+%Y%m%d').log" if [[ ! -f "$today_log" ]]; then echo "No log file found for today: $today_log" echo "" echo "Available log files:" ls -lh "${LOG_BASE_DIR}"/oracle_admin_*.log 2>/dev/null || echo " No log files found" else echo "Displaying last 50 lines of today's log:" echo "Log file: $today_log" echo "" echo "----------------------------------------" tail -50 "$today_log" echo "----------------------------------------" fi echo "" read -p "Press Enter to continue..." } ################################################################################ # MAIN PROGRAM LOOP ################################################################################ main() { # Validate prerequisites if ! validate_prerequisites; then echo "ERROR: Prerequisites validation failed" exit 1 fi # Main loop while true; do display_main_menu read -p "Select option: " choice case $choice in 1) handle_database_health_check ;; 2) handle_data_guard_status ;; 3) handle_data_guard_switchover ;; 4) handle_restore_point_management ;; 5) handle_view_all_databases ;; 6) handle_reload_database_list ;; 7) handle_view_logs ;; 0) clear echo "" echo "Exiting Oracle RAC Administration System..." echo "Goodbye!" echo "" exit 0 ;; *) echo "" echo "Invalid option. Please try again." sleep 2 ;; esac done } # Start the program main
#!/bin/bash ################################################################################ # Oracle 19c RAC Database Administration - Restore Point Management Functions # Description: Guaranteed restore point management for Data Guard environments # Created: 2025-11-02 # Updated: 2025-11-14 - Integrated with database loading fix ################################################################################ ################################################################################ # Function: manage_restore_points # Description: Interactive restore point management menu # Parameters: $1 - Database name # Returns: 0 on success, 1 on failure ################################################################################ manage_restore_points() { local db_name=$1 log_message "INFO" "Starting restore point management for $db_name" while true; do clear echo "==========================================" echo "Restore Point Management" echo "Database: $db_name" echo "==========================================" echo "" echo "1. List Restore Points" echo "2. Create Restore Point" echo "3. Create Guaranteed Restore Point" echo "4. Drop Restore Point" echo "5. Flashback Database to Restore Point" echo "6. Propagate Restore Point to Standby" echo "7. View Restore Point Details" echo "0. Return to Main Menu" echo "" echo "==========================================" echo "" read -p "Enter your choice [0-7]: " choice case $choice in 1) list_restore_points "$db_name" ;; 2) create_restore_point "$db_name" "NO" ;; 3) create_restore_point "$db_name" "YES" ;; 4) drop_restore_point "$db_name" ;; 5) flashback_to_restore_point "$db_name" ;; 6) propagate_restore_point_to_standby "$db_name" ;; 7) view_restore_point_details "$db_name" ;; 0) log_message "INFO" "Exiting restore point management" return 0 ;; *) echo "" echo "ERROR: Invalid choice" sleep 2 ;; esac if [[ $choice -ne 0 ]]; then echo "" read -p "Press Enter to continue..." fi done } ################################################################################ # Function: list_restore_points # Description: List all restore points ################################################################################ list_restore_points() { local db_name=$1 echo "" echo "==========================================" echo "Restore Points for $db_name" echo "==========================================" echo "" sqlplus -s / as sysdba << EOF SET LINESIZE 200 SET PAGESIZE 100 COLUMN name FORMAT A30 COLUMN scn FORMAT 999999999999999 COLUMN time FORMAT A20 COLUMN guarantee_flashback_database FORMAT A10 HEADING "GUARANTEED" COLUMN storage_size FORMAT 999,999,999,999 HEADING "STORAGE_MB" SELECT name, scn, TO_CHAR(time, 'YYYY-MM-DD HH24:MI:SS') as time, guarantee_flashback_database, ROUND(storage_size/1024/1024, 2) as storage_size FROM v\$restore_point ORDER BY time DESC; EXIT; EOF log_message "INFO" "Listed restore points for $db_name" } ################################################################################ # Function: create_restore_point # Description: Create a restore point # Parameters: $1 - Database name # $2 - Guarantee flashback (YES/NO) ################################################################################ create_restore_point() { local db_name=$1 local guarantee=$2 echo "" echo "==========================================" echo "Create Restore Point" echo "==========================================" echo "" # Check if flashback is enabled local flashback_on=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT flashback_on FROM v\$database; EXIT; EOF ) flashback_on=$(echo "$flashback_on" | xargs) if [[ "$flashback_on" != "YES" ]] && [[ "$guarantee" == "YES" ]]; then echo "ERROR: Flashback Database is not enabled" echo "Cannot create guaranteed restore point without flashback database" log_message "ERROR" "Flashback database not enabled on $db_name" return 1 fi # Get restore point name local default_name="RP_$(date '+%Y%m%d_%H%M%S')" echo "Default name: $default_name" read -p "Enter restore point name [default: $default_name]: " rp_name if [[ -z "$rp_name" ]]; then rp_name=$default_name fi # Confirm creation echo "" echo "Restore Point Details:" echo " Name: $rp_name" echo " Guaranteed: $guarantee" echo " Database: $db_name" echo "" read -p "Create this restore point? (yes/no): " confirm if [[ ! "$confirm" =~ ^[Yy][Ee][Ss]$ ]]; then echo "Restore point creation cancelled" return 0 fi echo "" echo "Creating restore point..." # Create restore point if [[ "$guarantee" == "YES" ]]; then sqlplus -s / as sysdba << EOF WHENEVER SQLERROR EXIT 1 CREATE RESTORE POINT ${rp_name} GUARANTEE FLASHBACK DATABASE; EXIT; EOF else sqlplus -s / as sysdba << EOF WHENEVER SQLERROR EXIT 1 CREATE RESTORE POINT ${rp_name}; EXIT; EOF fi if [[ $? -eq 0 ]]; then echo "✓ Restore point created successfully: $rp_name" log_message "SUCCESS" "Restore point created: $rp_name (guaranteed: $guarantee)" # Show details echo "" echo "Restore Point Details:" sqlplus -s / as sysdba << EOF SET LINESIZE 200 SELECT name, scn, TO_CHAR(time, 'YYYY-MM-DD HH24:MI:SS') as time, guarantee_flashback_database FROM v\$restore_point WHERE name = '${rp_name}'; EXIT; EOF else echo "✗ Failed to create restore point" log_message "ERROR" "Failed to create restore point: $rp_name" return 1 fi return 0 } ################################################################################ # Function: drop_restore_point # Description: Drop a restore point ################################################################################ drop_restore_point() { local db_name=$1 echo "" echo "==========================================" echo "Drop Restore Point" echo "==========================================" echo "" # List existing restore points echo "Existing Restore Points:" echo "------------------------" sqlplus -s / as sysdba << EOF SET LINESIZE 200 COLUMN name FORMAT A30 SELECT name, TO_CHAR(time, 'YYYY-MM-DD HH24:MI:SS') as time, guarantee_flashback_database as guaranteed FROM v\$restore_point ORDER BY time DESC; EXIT; EOF echo "" read -p "Enter restore point name to drop: " rp_name if [[ -z "$rp_name" ]]; then echo "No restore point name provided" return 0 fi # Verify restore point exists local exists=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT COUNT(*) FROM v\$restore_point WHERE name = '${rp_name}'; EXIT; EOF ) exists=$(echo "$exists" | xargs) if [[ $exists -eq 0 ]]; then echo "ERROR: Restore point '$rp_name' not found" return 1 fi # Confirm deletion echo "" echo "WARNING: This will permanently remove the restore point: $rp_name" read -p "Are you sure? (yes/no): " confirm if [[ ! "$confirm" =~ ^[Yy][Ee][Ss]$ ]]; then echo "Operation cancelled" return 0 fi echo "" echo "Dropping restore point..." sqlplus -s / as sysdba << EOF WHENEVER SQLERROR EXIT 1 DROP RESTORE POINT ${rp_name}; EXIT; EOF if [[ $? -eq 0 ]]; then echo "✓ Restore point dropped successfully: $rp_name" log_message "SUCCESS" "Restore point dropped: $rp_name" else echo "✗ Failed to drop restore point" log_message "ERROR" "Failed to drop restore point: $rp_name" return 1 fi return 0 } ################################################################################ # Function: flashback_to_restore_point # Description: Flashback database to a restore point ################################################################################ flashback_to_restore_point() { local db_name=$1 echo "" echo "==========================================" echo "Flashback Database to Restore Point" echo "==========================================" echo "" echo "⚠ WARNING: This is a critical operation!" echo "" # Check database role local db_role=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT database_role FROM v\$database; EXIT; EOF ) db_role=$(echo "$db_role" | xargs) echo "Current database role: $db_role" echo "" if [[ "$db_role" == "PRIMARY" ]]; then echo "⚠ WARNING: Flashback on PRIMARY database affects entire Data Guard configuration" echo "" fi # List restore points echo "Available Restore Points:" echo "-------------------------" sqlplus -s / as sysdba << EOF SET LINESIZE 200 SELECT name, scn, TO_CHAR(time, 'YYYY-MM-DD HH24:MI:SS') as time, guarantee_flashback_database FROM v\$restore_point ORDER BY time DESC; EXIT; EOF echo "" read -p "Enter restore point name: " rp_name if [[ -z "$rp_name" ]]; then echo "No restore point name provided" return 0 fi # Verify restore point exists local exists=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT COUNT(*) FROM v\$restore_point WHERE name = '${rp_name}'; EXIT; EOF ) exists=$(echo "$exists" | xargs) if [[ $exists -eq 0 ]]; then echo "ERROR: Restore point '$rp_name' not found" return 1 fi # Final confirmation echo "" echo "==========================================" echo "FLASHBACK CONFIRMATION" echo "==========================================" echo "Database: $db_name" echo "Restore Point: $rp_name" echo "" echo "This operation will:" echo " 1. Shutdown the database" echo " 2. Mount the database" echo " 3. Flashback to restore point" echo " 4. Open database with RESETLOGS" echo "" echo "⚠ All data changes after the restore point will be LOST!" echo "" read -p "Type 'FLASHBACK' to proceed: " confirm if [[ "$confirm" != "FLASHBACK" ]]; then echo "Operation cancelled" return 0 fi echo "" echo "Performing flashback operation..." log_message "INFO" "Starting flashback database to $rp_name" # Execute flashback sqlplus -s / as sysdba << EOF WHENEVER SQLERROR EXIT 1 PROMPT Shutting down database... SHUTDOWN IMMEDIATE; PROMPT Starting database in MOUNT mode... STARTUP MOUNT; PROMPT Executing flashback database... FLASHBACK DATABASE TO RESTORE POINT ${rp_name}; PROMPT Opening database with RESETLOGS... ALTER DATABASE OPEN RESETLOGS; EXIT; EOF if [[ $? -eq 0 ]]; then echo "" echo "✓ Flashback completed successfully" echo "" echo "Database has been restored to restore point: $rp_name" echo "" if [[ "$db_role" == "PRIMARY" ]]; then echo "IMPORTANT: Reinstate standby databases in Data Guard configuration" echo "" echo "For each standby database, execute:" echo " 1. Connect to standby" echo " 2. SHUTDOWN IMMEDIATE;" echo " 3. STARTUP MOUNT;" echo " 4. FLASHBACK DATABASE TO RESTORE POINT $rp_name;" echo " 5. ALTER DATABASE OPEN;" echo " 6. Restart MRP: ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT;" echo "" fi log_message "SUCCESS" "Flashback database completed: $rp_name" else echo "✗ Flashback operation failed" log_message "ERROR" "Flashback database failed: $rp_name" return 1 fi return 0 } ################################################################################ # Function: propagate_restore_point_to_standby # Description: Ensure restore point is propagated to standby databases ################################################################################ propagate_restore_point_to_standby() { local db_name=$1 echo "" echo "==========================================" echo "Propagate Restore Point to Standby" echo "==========================================" echo "" # Check database role local db_role=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT database_role FROM v\$database; EXIT; EOF ) db_role=$(echo "$db_role" | xargs) if [[ "$db_role" != "PRIMARY" ]]; then echo "ERROR: This operation must be run on PRIMARY database" echo "Current role: $db_role" return 1 fi # List restore points echo "Available Restore Points:" echo "-------------------------" sqlplus -s / as sysdba << EOF SET LINESIZE 200 SELECT name, TO_CHAR(time, 'YYYY-MM-DD HH24:MI:SS') as time FROM v\$restore_point ORDER BY time DESC; EXIT; EOF echo "" read -p "Enter restore point name to propagate: " rp_name if [[ -z "$rp_name" ]]; then echo "No restore point name provided" return 0 fi echo "" echo "Checking restore point status..." # Get restore point SCN local rp_scn=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT scn FROM v\$restore_point WHERE name = '${rp_name}'; EXIT; EOF ) rp_scn=$(echo "$rp_scn" | xargs) if [[ -z "$rp_scn" ]]; then echo "ERROR: Restore point '$rp_name' not found" return 1 fi echo "Restore point SCN: $rp_scn" echo "" echo "To verify on standby database:" echo " 1. Connect to standby database" echo " 2. SELECT * FROM v\$restore_point WHERE name = '${rp_name}';" echo "" echo "If not present, ensure:" echo " - Redo transport is working" echo " - MRP is applying redo" echo " - There are no archive log gaps" echo "" # Check Data Guard status echo "Data Guard Transport Status:" echo "----------------------------" sqlplus -s / as sysdba << EOF SET LINESIZE 200 SELECT dest_name, status, error FROM v\$archive_dest_status WHERE dest_name LIKE 'LOG_ARCHIVE_DEST_%' AND status != 'INACTIVE'; EXIT; EOF log_message "INFO" "Checked restore point propagation for $rp_name" } ################################################################################ # Function: view_restore_point_details # Description: View detailed information about restore points ################################################################################ view_restore_point_details() { local db_name=$1 echo "" echo "==========================================" echo "Restore Point Details" echo "==========================================" echo "" sqlplus -s / as sysdba << EOF SET LINESIZE 200 SET PAGESIZE 100 COLUMN name FORMAT A30 COLUMN scn FORMAT 999999999999999 COLUMN time FORMAT A20 COLUMN database_incarnation# FORMAT 9999 HEADING "INCARN" COLUMN guarantee_flashback_database FORMAT A10 HEADING "GUARANTEED" COLUMN storage_size FORMAT 999,999,999,999 HEADING "STORAGE_MB" SELECT name, scn, TO_CHAR(time, 'YYYY-MM-DD HH24:MI:SS') as time, database_incarnation#, guarantee_flashback_database, ROUND(storage_size/1024/1024, 2) as storage_size FROM v\$restore_point ORDER BY time DESC; PROMPT PROMPT Flashback Database Status: PROMPT ========================== SELECT flashback_on, log_mode, TO_CHAR(oldest_flashback_time, 'YYYY-MM-DD HH24:MI:SS') as oldest_flashback_time, TO_CHAR(oldest_flashback_scn) as oldest_flashback_scn FROM v\$database; PROMPT PROMPT Fast Recovery Area Usage: PROMPT ========================= SELECT space_limit/1024/1024/1024 as space_limit_gb, space_used/1024/1024/1024 as space_used_gb, space_reclaimable/1024/1024/1024 as space_reclaimable_gb, number_of_files, ROUND((space_used/space_limit)*100, 2) as percent_used FROM v\$recovery_file_dest; EXIT; EOF log_message "INFO" "Viewed restore point details for $db_name" } ################################################################################ # Function: cleanup_old_restore_points # Description: Clean up old restore points ################################################################################ cleanup_old_restore_points() { local db_name=$1 local days_old=${2:-7} echo "" echo "==========================================" echo "Cleanup Old Restore Points" echo "==========================================" echo "" echo "Removing restore points older than $days_old days..." echo "" # List old restore points local old_rps=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT name FROM v\$restore_point WHERE time < SYSDATE - $days_old AND guarantee_flashback_database = 'YES'; EXIT; EOF ) if [[ -z "$old_rps" ]]; then echo "No old restore points found" return 0 fi echo "Old restore points to be removed:" echo "$old_rps" echo "" read -p "Proceed with cleanup? (yes/no): " confirm if [[ ! "$confirm" =~ ^[Yy][Ee][Ss]$ ]]; then echo "Cleanup cancelled" return 0 fi # Drop each restore point while IFS= read -r rp_name; do rp_name=$(echo "$rp_name" | xargs) if [[ -n "$rp_name" ]]; then echo "Dropping: $rp_name" sqlplus -s / as sysdba << EOF DROP RESTORE POINT ${rp_name}; EXIT; EOF if [[ $? -eq 0 ]]; then echo " ✓ Dropped: $rp_name" log_message "INFO" "Dropped old restore point: $rp_name" else echo " ✗ Failed: $rp_name" log_message "ERROR" "Failed to drop restore point: $rp_name" fi fi done <<< "$old_rps" echo "" echo "Cleanup completed" } ################################################################################ # Export functions ################################################################################ export -f manage_restore_points export -f list_restore_points export -f create_restore_point export -f drop_restore_point export -f flashback_to_restore_point export -f cleanup_old_restore_points log_message "INFO" "Restore point management functions loaded"
#!/bin/bash ################################################################################ # Oracle 19c RAC Database Administration - Data Guard Switchover Functions # Description: Data Guard switchover with automatic rollback capability # Created: 2025-11-02 # Updated: 2025-11-14 - Integrated with database loading fix and wrapper functions ################################################################################ ################################################################################ # Function: perform_dg_switchover # Description: Main wrapper function for Data Guard switchover # Parameters: $1 - Primary database name # $2 - Primary SCAN address # $3 - Primary service name # $4 - Standby database name # $5 - Standby SCAN address # $6 - Standby service name # Returns: 0 on success, 1 on failure ################################################################################ perform_dg_switchover() { local primary_db=$1 local primary_scan=$2 local primary_service=$3 local standby_db=$4 local standby_scan=$5 local standby_service=$6 log_message "INFO" "Starting Data Guard switchover process" log_message "INFO" "Primary: ${primary_db} @ ${primary_scan}/${primary_service}" log_message "INFO" "Standby: ${standby_db} @ ${standby_scan}/${standby_service}" # Validate connections to both databases echo "" echo "Validating database connections..." if ! test_db_connection "${primary_scan}" "${primary_service}"; then log_message "ERROR" "Cannot connect to primary database ${primary_db}" echo "ERROR: Cannot connect to primary database ${primary_db}" return 1 fi echo "✓ Primary database connection OK" if ! test_db_connection "${standby_scan}" "${standby_service}"; then log_message "ERROR" "Cannot connect to standby database ${standby_db}" echo "ERROR: Cannot connect to standby database ${standby_db}" return 1 fi echo "✓ Standby database connection OK" # Call the actual switchover function perform_switchover "$primary_db" "$standby_db" return $? } ################################################################################ # Function: perform_switchover # Description: Perform Data Guard switchover with validation and rollback # Parameters: $1 - Current primary database name # $2 - Target standby database name (new primary) # Returns: 0 on success, 1 on failure ################################################################################ perform_switchover() { local current_primary=$1 local target_standby=$2 log_message "INFO" "==========================================" log_message "INFO" "Starting Data Guard Switchover" log_message "INFO" "Current Primary: $current_primary" log_message "INFO" "Target Primary: $target_standby" log_message "INFO" "==========================================" # Generate switchover report file local timestamp=$(date '+%Y%m%d_%H%M%S') local report_file="${REPORT_BASE_DIR}/switchover_${current_primary}_${timestamp}.log" # Create restore point for rollback local restore_point="SWITCHOVER_${timestamp}" echo "" echo "==========================================" echo "Data Guard Switchover Process" echo "==========================================" echo "" echo "Phase 1: Pre-Switchover Validation" echo "-----------------------------------" # Pre-switchover validation if ! validate_switchover_prerequisites "$current_primary" "$target_standby"; then log_message "ERROR" "Pre-switchover validation failed" echo "✗ Pre-switchover validation FAILED" echo " Switchover cannot proceed safely" return 1 fi echo "✓ Pre-switchover validation PASSED" log_message "INFO" "Pre-switchover validation passed" # Create restore points for rollback echo "" echo "Phase 2: Creating Restore Points" echo "---------------------------------" if ! create_switchover_restore_points "$current_primary" "$restore_point"; then log_message "ERROR" "Failed to create restore points" echo "✗ Failed to create restore points" echo " Switchover aborted for safety" return 1 fi echo "✓ Restore points created successfully" log_message "INFO" "Restore points created: $restore_point" # Perform the switchover echo "" echo "Phase 3: Executing Switchover" echo "------------------------------" if ! execute_switchover "$current_primary" "$target_standby"; then log_message "ERROR" "Switchover execution failed" echo "✗ Switchover FAILED" echo "" echo "Attempting automatic rollback..." if perform_switchover_rollback "$current_primary" "$target_standby" "$restore_point"; then echo "✓ Rollback successful - databases restored to original state" log_message "INFO" "Switchover rolled back successfully" else echo "✗ Rollback FAILED - manual intervention required" log_message "ERROR" "Switchover rollback failed - manual intervention needed" fi return 1 fi echo "✓ Switchover executed successfully" log_message "INFO" "Switchover execution completed" # Post-switchover validation echo "" echo "Phase 4: Post-Switchover Validation" echo "------------------------------------" if ! validate_post_switchover "$target_standby" "$current_primary"; then log_message "ERROR" "Post-switchover validation failed" echo "✗ Post-switchover validation FAILED" echo " WARNING: Databases may not be in sync" echo "" echo "Attempting automatic rollback..." if perform_switchover_rollback "$target_standby" "$current_primary" "$restore_point"; then echo "✓ Rollback successful - databases restored" log_message "INFO" "Post-validation failure - rolled back successfully" else echo "✗ Rollback FAILED - manual intervention required" log_message "ERROR" "Post-validation rollback failed" fi return 1 fi echo "✓ Post-switchover validation PASSED" log_message "INFO" "Post-switchover validation successful" # Generate switchover report generate_switchover_report "$current_primary" "$target_standby" "$timestamp" "SUCCESS" echo "" echo "==========================================" echo "Switchover Completed Successfully" echo "==========================================" echo "" echo "New Configuration:" echo " Primary: $target_standby" echo " Standby: $current_primary" echo "" echo "Report: $report_file" echo "" log_message "INFO" "==========================================" log_message "INFO" "Switchover completed successfully" log_message "INFO" "New Primary: $target_standby" log_message "INFO" "==========================================" return 0 } ################################################################################ # Function: validate_switchover_prerequisites # Description: Validate that switchover can be performed safely ################################################################################ validate_switchover_prerequisites() { local current_primary=$1 local target_standby=$2 log_message "INFO" "Validating switchover prerequisites" echo " Checking primary database status..." # Check primary database role local primary_role=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT database_role FROM v\$database; EXIT; EOF ) primary_role=$(echo "$primary_role" | xargs) if [[ "$primary_role" != "PRIMARY" ]]; then echo " ✗ Database is not in PRIMARY role (current: $primary_role)" log_message "ERROR" "Database not in PRIMARY role: $primary_role" return 1 fi echo " ✓ Database is in PRIMARY role" # Check switchover status local switchover_status=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT switchover_status FROM v\$database; EXIT; EOF ) switchover_status=$(echo "$switchover_status" | xargs) if [[ "$switchover_status" != "TO STANDBY" && "$switchover_status" != "SESSIONS ACTIVE" ]]; then echo " ✗ Switchover not allowed (status: $switchover_status)" log_message "ERROR" "Invalid switchover status: $switchover_status" return 1 fi echo " ✓ Switchover status is valid: $switchover_status" # Check for active sessions if needed if [[ "$switchover_status" == "SESSIONS ACTIVE" ]]; then echo " ⚠ Warning: Active sessions present - will be terminated during switchover" log_message "WARN" "Active sessions present during switchover" fi # Check Data Guard configuration echo " Checking Data Guard configuration..." local dg_status=$(dgmgrl / << EOF SHOW CONFIGURATION; EXIT; EOF ) if echo "$dg_status" | grep -q "SUCCESS"; then echo " ✓ Data Guard configuration is valid" else echo " ✗ Data Guard configuration has issues" log_message "ERROR" "Data Guard configuration validation failed" return 1 fi # Check standby database status via DGMGRL echo " Checking standby database status..." local standby_status=$(dgmgrl / << EOF SHOW DATABASE $target_standby; EXIT; EOF ) if echo "$standby_status" | grep -q "SUCCESS"; then echo " ✓ Standby database is ready" else echo " ✗ Standby database has issues" echo "$standby_status" log_message "ERROR" "Standby database validation failed" return 1 fi # Check apply lag echo " Checking apply lag..." local apply_lag=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT value FROM v\$dataguard_stats WHERE name = 'apply lag'; EXIT; EOF ) apply_lag=$(echo "$apply_lag" | xargs) if [[ -n "$apply_lag" ]]; then echo " Apply lag: $apply_lag" log_message "INFO" "Current apply lag: $apply_lag" else echo " ✓ No significant apply lag" fi # Check for archive gaps echo " Checking for archive log gaps..." local gap_count=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT COUNT(*) FROM v\$archive_gap; EXIT; EOF ) gap_count=$(echo "$gap_count" | xargs) if [[ $gap_count -gt 0 ]]; then echo " ✗ Archive log gaps detected: $gap_count" log_message "ERROR" "Archive log gaps present: $gap_count" return 1 fi echo " ✓ No archive log gaps" log_message "INFO" "All prerequisite checks passed" return 0 } ################################################################################ # Function: create_switchover_restore_points # Description: Create restore points for rollback capability ################################################################################ create_switchover_restore_points() { local current_primary=$1 local restore_point=$2 log_message "INFO" "Creating restore points for rollback" echo " Creating restore point on primary: $restore_point" # Create guaranteed restore point on primary sqlplus -s / as sysdba << EOF WHENEVER SQLERROR EXIT 1 CREATE RESTORE POINT ${restore_point} GUARANTEE FLASHBACK DATABASE; EXIT; EOF if [[ $? -eq 0 ]]; then echo " ✓ Restore point created on primary" log_message "INFO" "Restore point created on primary: $restore_point" else echo " ✗ Failed to create restore point on primary" log_message "ERROR" "Failed to create restore point on primary" return 1 fi # Note: Restore point will be automatically created on standby via redo apply echo " Restore point will replicate to standby via redo apply" return 0 } ################################################################################ # Function: execute_switchover # Description: Execute the actual switchover operation ################################################################################ execute_switchover() { local current_primary=$1 local target_standby=$2 log_message "INFO" "Executing switchover from $current_primary to $target_standby" echo " Step 1: Switching $current_primary to standby role..." # Switchover current primary to standby local switch_result=$(dgmgrl / << EOF SWITCHOVER TO $target_standby; EXIT; EOF ) echo "$switch_result" if echo "$switch_result" | grep -q "SUCCESS"; then echo " ✓ Switchover command executed successfully" log_message "INFO" "Switchover command successful" else echo " ✗ Switchover command failed" log_message "ERROR" "Switchover command failed" return 1 fi # Wait for switchover to complete echo " Step 2: Waiting for switchover to complete..." sleep 10 # Verify new primary is open echo " Step 3: Verifying new primary database..." local retries=0 local max_retries=30 while [[ $retries -lt $max_retries ]]; do local db_status=$(dgmgrl / << EOF SHOW DATABASE $target_standby; EXIT; EOF ) if echo "$db_status" | grep -q "PRIMARY" && echo "$db_status" | grep -q "SUCCESS"; then echo " ✓ New primary database is open and ready" log_message "INFO" "New primary database confirmed: $target_standby" return 0 fi ((retries++)) echo " Waiting for database to transition... (attempt $retries/$max_retries)" sleep 5 done echo " ✗ Timeout waiting for database transition" log_message "ERROR" "Timeout waiting for new primary to be ready" return 1 } ################################################################################ # Function: validate_post_switchover # Description: Validate switchover completed successfully ################################################################################ validate_post_switchover() { local new_primary=$1 local new_standby=$2 log_message "INFO" "Validating post-switchover state" echo " Checking new configuration..." # Show configuration local config=$(dgmgrl / << EOF SHOW CONFIGURATION; EXIT; EOF ) echo "$config" if echo "$config" | grep -q "$new_primary" | grep -q "PRIMARY"; then echo " ✓ New primary confirmed: $new_primary" else echo " ✗ New primary validation failed" log_message "ERROR" "New primary validation failed" return 1 fi if echo "$config" | grep -q "$new_standby" | grep -q "STANDBY"; then echo " ✓ New standby confirmed: $new_standby" else echo " ✗ New standby validation failed" log_message "ERROR" "New standby validation failed" return 1 fi # Check for errors if echo "$config" | grep -qi "error\|warning"; then echo " ⚠ Configuration warnings/errors detected" log_message "WARN" "Post-switchover configuration has warnings" else echo " ✓ No configuration errors detected" fi # Verify redo transport is working echo " Checking redo transport..." sleep 5 local transport_check=$(dgmgrl / << EOF SHOW DATABASE $new_standby; EXIT; EOF ) if echo "$transport_check" | grep -q "SUCCESS"; then echo " ✓ Redo transport is active" else echo " ⚠ Redo transport may have issues" log_message "WARN" "Redo transport validation inconclusive" fi log_message "INFO" "Post-switchover validation completed" return 0 } ################################################################################ # Function: perform_switchover_rollback # Description: Rollback switchover using restore points ################################################################################ perform_switchover_rollback() { local current_primary=$1 local current_standby=$2 local restore_point=$3 log_message "INFO" "==========================================" log_message "INFO" "Starting Switchover Rollback" log_message "INFO" "==========================================" echo "" echo "Rollback Process:" echo "=================" # Attempt to switchover back echo " Attempting to switch back to original configuration..." local rollback_result=$(dgmgrl / << EOF SWITCHOVER TO $current_standby; EXIT; EOF ) if echo "$rollback_result" | grep -q "SUCCESS"; then echo " ✓ Switchover rollback successful" log_message "INFO" "Switchover rolled back successfully" # Wait for completion sleep 10 # Verify original configuration echo " Verifying original configuration restored..." local verify=$(dgmgrl / << EOF SHOW CONFIGURATION; EXIT; EOF ) if echo "$verify" | grep -q "$current_standby" | grep -q "PRIMARY"; then echo " ✓ Original configuration confirmed" log_message "INFO" "Original configuration restored" return 0 else echo " ⚠ Configuration verification inconclusive" log_message "WARN" "Rollback verification inconclusive" return 1 fi else echo " ✗ Automatic rollback failed" log_message "ERROR" "Automatic switchover rollback failed" echo "" echo "Manual Rollback Required:" echo "========================" echo "1. Connect to $current_primary" echo "2. Execute: FLASHBACK DATABASE TO RESTORE POINT $restore_point;" echo "3. Execute: ALTER DATABASE OPEN RESETLOGS;" echo "4. Reconfigure Data Guard" echo "" return 1 fi } ################################################################################ # Function: generate_switchover_report # Description: Generate detailed switchover report ################################################################################ generate_switchover_report() { local old_primary=$1 local new_primary=$2 local timestamp=$3 local status=$4 local report_file="${REPORT_BASE_DIR}/switchover_${old_primary}_${timestamp}.html" { cat << EOF Data Guard Switchover Report

Data Guard Switchover Report

Generated: $(date '+%Y-%m-%d %H:%M:%S')

Status: $status

Switchover Summary

ItemValue
Original Primary$old_primary
New Primary$new_primary
Switchover Time$(date '+%Y-%m-%d %H:%M:%S')
Status$status

Current Configuration

EOF
        dgmgrl / << DGEOF
SHOW CONFIGURATION;
EXIT;
DGEOF
        
        echo "
" echo "
" generate_html_footer } > "$report_file" log_message "INFO" "Switchover report generated: $report_file" } ################################################################################ # Export functions ################################################################################ export -f perform_switchover export -f validate_switchover_prerequisites export -f execute_switchover export -f perform_switchover_rollback log_message "INFO" "Data Guard switchover functions loaded"

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 ################################################################################
#!/bin/bash ################################################################################ # Oracle 19c Database Health Check Functions # Description: Functions for performing database health checks # Version: 2.0 (Integrated + Enhanced) # Created: 2025-11-02 # Updated: 2025-11-14 - Merged with enhanced version and database loading fix ################################################################################ # NOTE: This file assumes functions_common.sh has already been sourced # by the calling script (oracle_rac_admin.sh) ################################################################################ # Function: perform_db_health_check # Description: Performs health check on a single database # Parameters: $1 - Database name # $2 - SCAN address # $3 - Service name ################################################################################ perform_db_health_check() { local db_name=$1 local scan=$2 local service=$3 log_message "INFO" "Starting health check for database: ${db_name}" # Validate database connection first if ! test_db_connection "${scan}" "${service}"; then log_message "ERROR" "Cannot connect to database ${db_name}" echo "ERROR: Cannot connect to database ${db_name}" return 1 fi # Generate report filename local timestamp=$(date '+%Y%m%d_%H%M%S') local report_file="${REPORT_BASE_DIR}/health_check_${db_name}_${timestamp}.html" # Create HTML report { generate_health_report_header "$db_name" echo "
" echo "

Health Check Summary

" echo "

Database: $db_name

" echo "

SCAN Host: $scan

" echo "

Service: $service

" echo "

Check Time: $(date)

" # Run health check sections check_database_status "$db_name" check_instance_status "$db_name" check_tablespace_usage "$db_name" check_invalid_objects "$db_name" check_asm_diskgroups "$db_name" check_database_size "$db_name" check_alert_log_errors "$db_name" check_backup_status "$db_name" check_archive_log_status "$db_name" echo "
" generate_html_footer } > "$report_file" log_message "SUCCESS" "Health check report generated: $report_file" echo "" echo "✓ Health check completed successfully" echo " Report: $report_file" echo "" # Display summary on console display_health_summary "$db_name" return 0 } ################################################################################ # Function: generate_health_report_header # Description: Generate HTML header for health check report ################################################################################ generate_health_report_header() { local db_name=$1 cat << EOF Health Check Report - $db_name

Database Health Check Report

$db_name

Generated: $(date '+%Y-%m-%d %H:%M:%S')

EOF } ################################################################################ # Function: check_database_status # Description: Check database status and role ################################################################################ check_database_status() { local db_name=$1 echo "

Database Status

" local status=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT name||'|'||db_unique_name||'|'||database_role||'|'||open_mode||'|'||log_mode||'|'||flashback_on||'|'||force_logging FROM v\$database; EXIT; EOF ) if [[ -n "$status" ]]; then IFS='|' read -r name unique_name role open_mode log_mode flashback force_log <<< "$status" echo "" echo "" echo "" echo "" echo "" echo "" echo "" echo "" echo "" echo "
MetricValueStatus
Database Name$nameOK
Unique Name$unique_nameOK
Database Role$roleOK
Open Mode$open_modeOK
Archive Log Mode$log_modeOK
Flashback Database$flashbackOK
Force Logging$force_logOK
" log_message "INFO" "Database status check completed for $db_name" else echo "

ERROR: Unable to retrieve database status

" log_message "ERROR" "Failed to retrieve database status for $db_name" fi } ################################################################################ # Function: check_instance_status # Description: Check RAC instance status ################################################################################ check_instance_status() { local db_name=$1 echo "

Instance Status

" sqlplus -s / as sysdba << EOF SET MARKUP HTML ON SET HEADING ON SET FEEDBACK OFF SELECT instance_name, host_name, version, status, database_status, instance_role, TO_CHAR(startup_time, 'YYYY-MM-DD HH24:MI:SS') as startup_time, ROUND((SYSDATE - startup_time)*24, 2) as uptime_hours FROM gv\$instance ORDER BY instance_name; EXIT; EOF log_message "INFO" "Instance status check completed for $db_name" } ################################################################################ # Function: check_tablespace_usage # Description: Check tablespace usage and alert on high usage ################################################################################ check_tablespace_usage() { local db_name=$1 echo "

Tablespace Usage

" sqlplus -s / as sysdba << EOF SET MARKUP HTML ON SET HEADING ON SET FEEDBACK OFF WITH ts_usage AS ( SELECT tablespace_name, ROUND(used_space * 8192 / 1024 / 1024 / 1024, 2) AS used_gb, ROUND(tablespace_size * 8192 / 1024 / 1024 / 1024, 2) AS total_gb, ROUND(used_percent, 2) AS used_percent, CASE WHEN used_percent >= 90 THEN 'CRITICAL' WHEN used_percent >= 80 THEN 'WARNING' ELSE 'OK' END AS status FROM dba_tablespace_usage_metrics ) SELECT * FROM ts_usage ORDER BY used_percent DESC; EXIT; EOF log_message "INFO" "Tablespace usage check completed for $db_name" } ################################################################################ # Function: check_invalid_objects # Description: Check for invalid database objects ################################################################################ check_invalid_objects() { local db_name=$1 echo "

Invalid Objects

" local invalid_count=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT COUNT(*) FROM dba_objects WHERE status = 'INVALID'; EXIT; EOF ) invalid_count=$(echo "$invalid_count" | xargs) if [[ $invalid_count -gt 0 ]]; then echo "

Found $invalid_count invalid objects

" sqlplus -s / as sysdba << EOF SET MARKUP HTML ON SET HEADING ON SET FEEDBACK OFF SELECT owner, object_type, COUNT(*) as invalid_count FROM dba_objects WHERE status = 'INVALID' GROUP BY owner, object_type ORDER BY invalid_count DESC; EXIT; EOF log_message "WARN" "Found $invalid_count invalid objects in $db_name" else echo "

✓ No invalid objects found

" log_message "INFO" "No invalid objects found in $db_name" fi } ################################################################################ # Function: check_asm_diskgroups # Description: Check ASM diskgroup status and usage ################################################################################ check_asm_diskgroups() { local db_name=$1 echo "

ASM Diskgroup Status

" # Check if ASM is being used local asm_check=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT COUNT(*) FROM v\$asm_diskgroup; EXIT; EOF ) asm_check=$(echo "$asm_check" | xargs) if [[ $asm_check -gt 0 ]]; then sqlplus -s / as sysdba << EOF SET MARKUP HTML ON SET HEADING ON SET FEEDBACK OFF SELECT name, state, type, total_mb, free_mb, ROUND((total_mb - free_mb) / total_mb * 100, 2) as used_percent, offline_disks FROM v\$asm_diskgroup ORDER BY name; EXIT; EOF log_message "INFO" "ASM diskgroup check completed for $db_name" else echo "

ASM not configured or not accessible

" log_message "INFO" "ASM not configured for $db_name" fi } ################################################################################ # Function: check_database_size # Description: Check database size and growth ################################################################################ check_database_size() { local db_name=$1 echo "

Database Size

" sqlplus -s / as sysdba << EOF SET MARKUP HTML ON SET HEADING ON SET FEEDBACK OFF SELECT ROUND(SUM(bytes)/1024/1024/1024, 2) AS total_size_gb, ROUND(SUM(CASE WHEN autoextensible = 'YES' THEN maxbytes ELSE bytes END)/1024/1024/1024, 2) AS max_size_gb FROM dba_data_files UNION ALL SELECT ROUND(SUM(bytes)/1024/1024/1024, 2) AS total_size_gb, ROUND(SUM(CASE WHEN autoextensible = 'YES' THEN maxbytes ELSE bytes END)/1024/1024/1024, 2) AS max_size_gb FROM dba_temp_files; EXIT; EOF log_message "INFO" "Database size check completed for $db_name" } ################################################################################ # Function: check_alert_log_errors # Description: Check for recent errors in alert log ################################################################################ check_alert_log_errors() { local db_name=$1 echo "

Recent Alert Log Errors (Last 24 Hours)

" sqlplus -s / as sysdba << EOF SET MARKUP HTML ON SET HEADING ON SET FEEDBACK OFF SELECT TO_CHAR(originating_timestamp, 'YYYY-MM-DD HH24:MI:SS') as error_time, message_text FROM v\$diag_alert_ext WHERE message_text LIKE '%ORA-%' AND originating_timestamp > SYSDATE - 1 ORDER BY originating_timestamp DESC FETCH FIRST 20 ROWS ONLY; EXIT; EOF log_message "INFO" "Alert log error check completed for $db_name" } ################################################################################ # Function: check_backup_status # Description: Check RMAN backup status ################################################################################ check_backup_status() { local db_name=$1 echo "

RMAN Backup Status

" sqlplus -s / as sysdba << EOF SET MARKUP HTML ON SET HEADING ON SET FEEDBACK OFF SELECT TO_CHAR(start_time, 'YYYY-MM-DD HH24:MI:SS') as backup_time, input_type, status, ROUND(elapsed_seconds/3600, 2) as hours, ROUND(input_bytes/1024/1024/1024, 2) as input_gb, ROUND(output_bytes/1024/1024/1024, 2) as output_gb FROM v\$rman_backup_job_details WHERE start_time > SYSDATE - 7 ORDER BY start_time DESC; EXIT; EOF log_message "INFO" "Backup status check completed for $db_name" } ################################################################################ # Function: check_archive_log_status # Description: Check archive log generation and disk usage ################################################################################ check_archive_log_status() { local db_name=$1 echo "

Archive Log Status (Last 7 Days)

" sqlplus -s / as sysdba << EOF SET MARKUP HTML ON SET HEADING ON SET FEEDBACK OFF SELECT TO_CHAR(first_time, 'YYYY-MM-DD') as log_date, COUNT(*) as log_count, ROUND(SUM(blocks * block_size)/1024/1024/1024, 2) as total_gb FROM v\$archived_log WHERE first_time > SYSDATE - 7 GROUP BY TO_CHAR(first_time, 'YYYY-MM-DD') ORDER BY log_date DESC; EXIT; EOF log_message "INFO" "Archive log status check completed for $db_name" } ################################################################################ # Function: display_health_summary # Description: Display health check summary on console ################################################################################ display_health_summary() { local db_name=$1 echo "==========================================" echo "Health Check Summary for $db_name" echo "==========================================" # Get key metrics local db_status=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT database_role||'|'||open_mode FROM v\$database; EXIT; EOF ) IFS='|' read -r role open_mode <<< "$db_status" echo "Database Role: $role" echo "Open Mode: $open_mode" # Check tablespace usage local critical_ts=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT COUNT(*) FROM dba_tablespace_usage_metrics WHERE used_percent >= 90; EXIT; EOF ) critical_ts=$(echo "$critical_ts" | xargs) if [[ $critical_ts -gt 0 ]]; then echo "Tablespaces: ⚠ WARNING - $critical_ts tablespace(s) above 90%" else echo "Tablespaces: ✓ OK" fi # Check invalid objects local invalid=$(sqlplus -s / as sysdba << EOF SET HEADING OFF SET FEEDBACK OFF SET PAGESIZE 0 SELECT COUNT(*) FROM dba_objects WHERE status = 'INVALID'; EXIT; EOF ) invalid=$(echo "$invalid" | xargs) if [[ $invalid -gt 0 ]]; then echo "Invalid Objects: ⚠ WARNING - $invalid invalid objects" else echo "Invalid Objects: ✓ OK" fi echo "==========================================" } ################################################################################ # Function: quick_health_check # Description: Quick health check for console display only ################################################################################ quick_health_check() { local db_name=$1 echo "" echo "Quick Health Check for $db_name" echo "========================================" sqlplus -s / as sysdba << EOF SET LINESIZE 200 SET PAGESIZE 100 PROMPT PROMPT Database Status: PROMPT ================ SELECT name, database_role, open_mode, log_mode FROM v\$database; PROMPT PROMPT Instance Status: PROMPT ================ SELECT instance_name, host_name, status, database_status FROM gv\$instance ORDER BY instance_name; PROMPT PROMPT Tablespace Usage (>80%): PROMPT ======================== SELECT tablespace_name, ROUND(used_percent, 2) as used_percent FROM dba_tablespace_usage_metrics WHERE used_percent > 80 ORDER BY used_percent DESC; PROMPT PROMPT Invalid Objects by Owner: PROMPT ======================== SELECT owner, COUNT(*) as invalid_count FROM dba_objects WHERE status = 'INVALID' GROUP BY owner ORDER BY invalid_count DESC; EXIT; EOF echo "" echo "========================================" } ################################################################################ # Export functions ################################################################################ export -f run_health_check export -f quick_health_check export -f display_health_summary log_message "INFO" "Database health check functions loaded"