#!/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
################################################################################