Sunday, November 9, 2025

#!/bin/bash ################################################################################ # Oracle 19c RAC Database Administration - Common Utility Functions # Description: Common utility functions used across all administration tasks # Created: 2025-11-02 ################################################################################ ################################################################################ # 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" >&2 ;; WARN) echo -e "\033[0;33m[${timestamp}] [${log_level}] ${message}\033[0m" >&2 ;; INFO) echo -e "\033[0;32m[${timestamp}] [${log_level}] ${message}\033[0m" >&2 ;; *) echo "[${timestamp}] [${log_level}] ${message}" >&2 ;; 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" "SQL*Plus not found at ${ORACLE_HOME}/bin/sqlplus" return 1 fi if [[ ! -f "${ORACLE_HOME}/bin/dgmgrl" ]]; then log_message "ERROR" "DGMGRL not found at ${ORACLE_HOME}/bin/dgmgrl" 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 necessary directories mkdir -p "${LOG_BASE_DIR}" "${REPORT_BASE_DIR}" log_message "INFO" "Prerequisites validation completed successfully" return 0 } ################################################################################ # Function: load_database_list # Description: Loads database configurations from the database list file # Parameters: $1 - Database name filter (optional, "ALL" for all databases) # Returns: Array of database configurations in format: DB_NAME|SCAN_HOST|SERVICE_NAME ################################################################################ load_database_list() { local db_filter=$1 local -a db_array=() log_message "INFO" "Loading database list from ${DATABASE_LIST_FILE}" while IFS='|' read -r db_name scan_host service_name || [[ -n "$db_name" ]]; do # Skip comments and empty lines [[ "$db_name" =~ ^#.*$ ]] && continue [[ -z "$db_name" ]] && continue # Trim whitespace db_name=$(echo "$db_name" | xargs) scan_host=$(echo "$scan_host" | xargs) service_name=$(echo "$service_name" | xargs) # Validate we have all required fields if [[ -z "$db_name" ]] || [[ -z "$scan_host" ]] || [[ -z "$service_name" ]]; then log_message "WARN" "Skipping incomplete entry: ${db_name}|${scan_host}|${service_name}" continue fi # Filter by database name if specified if [[ -z "$db_filter" ]] || [[ "$db_filter" == "ALL" ]] || [[ "$db_filter" == "$db_name" ]]; then db_array+=("${db_name}|${scan_host}|${service_name}") log_message "DEBUG" "Loaded database: ${db_name}" fi done < "${DATABASE_LIST_FILE}" if [[ ${#db_array[@]} -eq 0 ]]; then log_message "WARN" "No databases found matching filter: ${db_filter}" return 1 fi # Return array (print to stdout) printf '%s\n' "${db_array[@]}" return 0 } ################################################################################ # Function: test_db_connection # Description: Tests database connectivity using SQL*Plus # Parameters: $1 - SCAN hostname # $2 - Service name # Returns: 0 if connection successful, 1 if failed ################################################################################ test_db_connection() { local scan_host=$1 local service_name=$2 local connection_string="${SYS_USER}/${SYS_PASSWORD}@${scan_host}/${service_name} ${SYS_CONNECT_MODE}" log_message "INFO" "Testing database connection to ${scan_host}/${service_name}" # Mask password in logs local masked_conn="${SYS_USER}/***@${scan_host}/${service_name} ${SYS_CONNECT_MODE}" log_message "DEBUG" "Connection string: ${masked_conn}" # Test connection local test_result=$(timeout ${CONNECTION_TIMEOUT} ${ORACLE_HOME}/bin/sqlplus -S "${connection_string}" << EOF whenever sqlerror exit 1 set heading off feedback off select 'CONNECTION_SUCCESS' from dual; exit; EOF ) if echo "${test_result}" | grep -q "CONNECTION_SUCCESS"; then log_message "INFO" "Database connection successful" return 0 else log_message "ERROR" "Database connection failed: ${test_result}" return 1 fi } ################################################################################ # Function: get_db_role # Description: Determines if database is PRIMARY or STANDBY # Parameters: $1 - SCAN hostname # $2 - Service name # Returns: Prints "PRIMARY" or "STANDBY" or "UNKNOWN" ################################################################################ get_db_role() { local scan_host=$1 local service_name=$2 local connection_string="${SYS_USER}/${SYS_PASSWORD}@${scan_host}/${service_name} ${SYS_CONNECT_MODE}" log_message "DEBUG" "Determining database role for ${scan_host}/${service_name}" local db_role=$(${ORACLE_HOME}/bin/sqlplus -S "${connection_string}" << EOF whenever sqlerror exit 1 set heading off feedback off pagesize 0 select database_role from v\$database; exit; EOF ) db_role=$(echo "${db_role}" | xargs) if [[ "${db_role}" == "PRIMARY" ]] || [[ "${db_role}" == "PHYSICAL STANDBY" ]]; then echo "${db_role}" log_message "DEBUG" "Database role: ${db_role}" return 0 else echo "UNKNOWN" log_message "ERROR" "Unable to determine database role" return 1 fi } ################################################################################ # Function: send_email # Description: Sends email with inline HTML content using mail/mailx # Parameters: $1 - Subject # $2 - HTML content file path # $3 - Recipients (optional, uses default from config) ################################################################################ send_email() { local subject="$1" local html_file="$2" local recipients="${3:-$EMAIL_RECIPIENTS}" log_message "INFO" "Sending email: ${subject}" if [[ ! -f "${html_file}" ]]; then log_message "ERROR" "HTML file not found: ${html_file}" return 1 fi # Check if mail or mailx is available local mail_cmd="" if command -v mailx &> /dev/null; then mail_cmd="mailx" elif command -v mail &> /dev/null; then mail_cmd="mail" else log_message "ERROR" "Neither mail nor mailx command is available" return 1 fi log_message "DEBUG" "Using mail command: ${mail_cmd}" # Create a temporary file with proper MIME format local temp_email="/tmp/oracle_email_$$.txt" # Build proper MIME email cat > "${temp_email}" << EOF From: ${EMAIL_FROM} To: ${recipients} Subject: ${EMAIL_SUBJECT_PREFIX} ${subject} MIME-Version: 1.0 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: 8bit EOF # Append HTML content cat "${html_file}" >> "${temp_email}" # Send email using sendmail or mail -t local mail_result=1 # Try sendmail first (most reliable for HTML) if command -v sendmail &> /dev/null; then log_message "DEBUG" "Using sendmail for delivery" sendmail -t < "${temp_email}" mail_result=$? else # Fallback to mail/mailx with -t flag log_message "DEBUG" "Using ${mail_cmd} -t for delivery" ${mail_cmd} -t < "${temp_email}" mail_result=$? fi # Cleanup rm -f "${temp_email}" if [[ ${mail_result} -eq 0 ]]; then log_message "INFO" "Email sent successfully to ${recipients}" return 0 else log_message "ERROR" "Failed to send email" return 1 fi } ################################################################################ # Function: generate_html_header # Description: Generates HTML header for reports # Parameters: $1 - Report title # Returns: HTML header content ################################################################################ generate_html_header() { local title="$1" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') cat << EOF ${title}

${title}

Report Generated: ${timestamp}
Generated By: ${USER}@$(hostname)
EOF } ################################################################################ # Function: generate_html_footer # Description: Generates HTML footer for reports ################################################################################ generate_html_footer() { cat << EOF
EOF } ################################################################################ # Function: cleanup_old_files # Description: Cleans up old log and report files # 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 } ################################################################################ # End of Common Utility Functions ################################################################################