diff --git a/add-database.sh b/add-database.sh new file mode 100755 index 000000000..2f8e754ea --- /dev/null +++ b/add-database.sh @@ -0,0 +1,1247 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +echo Command used: +echo "$0 $@" +echo + +# +# Some variables +# +TIMESTAMP="$(date +%Y%m%d_%H%M%S)" + +# +# Playbooks +# +PB_CHECK_INSTANCE="check-instance.yml" +PB_CONFIG_DB="config-db.yml" +PB_CONFIG_RAC_DB="config-rac-db.yml" + +# +# Verify playbooks exist +# +for PBOOK in "${PB_CHECK_INSTANCE}" "${PB_CONFIG_DB}"; do + if [[ ! -f "${PBOOK}" ]]; then + printf "\n\033[1;31m%s\033[m\n\n" "The playbook ${PBOOK} does not exist; cannot continue." + exit 126 + else + if [[ -z "${PB_LIST}" ]]; then + PB_LIST="${PBOOK}" + else + PB_LIST="${PB_LIST} ${PBOOK}" + fi + fi +done + +# +# Inventory file (used to run the playbooks) +# +INVENTORY_DIR="./inventory_files" # Where to save the inventory files +INVENTORY_FILE="${INVENTORY_DIR}/inventory" # Default, the whole name will be built later using some parameters +INSTANCE_HOSTGROUP_NAME="dbasm" # Constant used for both SI and RAC installations +# +if [[ ! -d "${INVENTORY_DIR}" ]]; then + mkdir -p "${INVENTORY_DIR}" + if [ $? -eq 0 ]; then + printf "\n\033[1;36m%s\033[m\n\n" "Successfully created the ${INVENTORY_DIR} directory to save the inventory files." + else + printf "\n\033[1;31m%s\033[m\n\n" "Unable to create the ${INVENTORY_DIR} directory to save the inventory files; cannot continue." + exit 123 + fi +fi + +# +# Ansible logs directory, the logfile name is created later one +# +LOG_DIR="./logs" +LOG_FILE="${LOG_DIR}/log" +if [[ ! -d "${LOG_DIR}" ]]; then + mkdir -p "${LOG_DIR}" + if [ $? -eq 0 ]; then + printf "\n\033[1;36m%s\033[m\n\n" "Successfully created the ${LOG_DIR} directory to save the ansible logs." + else + printf "\n\033[1;31m%s\033[m\n\n" "Unable to create the ${LOG_DIR} directory to save the ansible logs; cannot continue." + exit 123 + fi +fi + +shopt -s nocasematch + +# Check if we're using the Mac stock getopt and fail if true +out="$(getopt -T)" +if [ $? != 4 ]; then + echo -e "Your getopt does not support long parameters, possibly you're on a Mac, if so please install gnu-getopt with brew" + echo -e "\thttps://brewformulas.org/Gnu-getopt" + exit +fi + +# +# Initialize PB_LIST variable before it's referenced +# +PB_LIST="check-instance.yml config-db.yml" + +# Parameter Definitions +ORA_DB_NAME="${ORA_DB_NAME:-ORCL}" +ORA_DB_NAME_PARAM="^[a-zA-Z0-9_$]+$" + +ORA_DB_DOMAIN="${ORA_DB_DOMAIN}" +ORA_DB_DOMAIN_PARAM="^[a-zA-Z0-9]*$" + +ORA_DB_CHARSET="${ORA_DB_CHARSET:-AL32UTF8}" +ORA_DB_CHARSET_PARAM="^.+$" + +ORA_DB_NCHARSET="${ORA_DB_NCHARSET:-AL16UTF16}" +ORA_DB_NCHARSET_PARAM="^.+$" + +ORA_DB_CONTAINER="${ORA_DB_CONTAINER:-TRUE}" +ORA_DB_CONTAINER_PARAM="^(TRUE|FALSE)$" + +ORA_DB_TYPE="${ORA_DB_TYPE:-MULTIPURPOSE}" +ORA_DB_TYPE_PARAM="MULTIPURPOSE|DATA_WAREHOUSING|OLTP" + +ORA_DB_HOME_DIR="${ORA_DB_HOME_DIR}" +ORA_DB_HOME_DIR_PARAM="^/.*" + +ORA_PDB_NAME_PREFIX="${ORA_PDB_NAME_PREFIX:-PDB}" +ORA_PDB_NAME_PREFIX_PARAM="^[a-zA-Z0-9]+$" + +ORA_PDB_COUNT="${ORA_PDB_COUNT:-1}" +ORA_PDB_COUNT_PARAM="^[0-9]+" + +ORA_REDO_LOG_SIZE="${ORA_REDO_LOG_SIZE:-100MB}" +ORA_REDO_LOG_SIZE_PARAM="^[0-9]+MB$" + +ORA_LISTENER_NAME="${ORA_LISTENER_NAME:-LISTENER}" +ORA_LISTENER_NAME_PARAM="^[a-zA-Z0-9_]+$" + +ORA_LISTENER_PORT="${ORA_LISTENER_PORT:-1521}" +ORA_LISTENER_PORT_PARAM="^[0-9]+$" + +ORA_DATA_DESTINATION="${ORA_DATA_DESTINATION:-DATA}" +ORA_DATA_DESTINATION_PARAM="^(\/|\+)?[a-zA-Z0-9]+$" + +ORA_RECO_DESTINATION="${ORA_RECO_DESTINATION:-RECO}" +ORA_RECO_DESTINATION_PARAM="^(\/|\+)?[a-zA-Z0-9]+$" + +SGA_TARGET="${SGA_TARGET}" +SGA_TARGET_PARAM="^([0-9]+[MG]|[0-9]+%)$" + +PGA_AGGREGATE_TARGET="${PGA_AGGREGATE_TARGET}" +PGA_AGGREGATE_TARGET_PARAM="^([0-9]+[MG]|[0-9]+%)$" + +MEMORY_PERCENT="${MEMORY_PERCENT}" +MEMORY_PERCENT_PARAM="^[0-9]+$" + +INSTANCE_IP_ADDR="${INSTANCE_IP_ADDR}" +INSTANCE_IP_ADDR_PARAM="[a-z0-9][a-z0-9\-\.]*" + +PRIMARY_IP_ADDR="${PRIMARY_IP_ADDR}" +PRIMARY_IP_ADDR_PARAM='^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' + +INSTANCE_SSH_USER="${INSTANCE_SSH_USER:-$(whoami)}" +INSTANCE_SSH_USER_PARAM="^[a-z0-9][a-z0-9_\-\.]*$" + +INSTANCE_HOSTNAME="${INSTANCE_HOSTNAME:-${INSTANCE_IP_ADDR}}" +INSTANCE_HOSTNAME_PARAM="^[a-z0-9]+$" + +INSTANCE_SSH_KEY="${INSTANCE_SSH_KEY:-~/.ssh/id_rsa}" +INSTANCE_SSH_KEY_PARAM="^.+$" + +INSTANCE_SSH_EXTRA_ARGS="${INSTANCE_SSH_EXTRA_ARGS:-'-o ServerAliveInterval=60 -o ServerAliveCountMax=3 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentityAgent=no'}" +INSTANCE_SSH_EXTRA_ARGS_PARAM="^.+$" + +BACKUP_DEST="${BACKUP_DEST}" +BACKUP_DEST_PARAM="^(\/|\+)?.*$" + +BACKUP_REDUNDANCY="${BACKUP_REDUNDANCY:-2}" +BACKUP_REDUNDANCY_PARAM="^[0-9]+$" + +ARCHIVE_REDUNDANCY="${ARCHIVE_REDUNDANCY:-2}" +ARCHIVE_REDUNDANCY_PARAM="^[0-9]+$" + +ARCHIVE_ONLINE_DAYS="${ARCHIVE_ONLINE_DAYS:-7}" +ARCHIVE_ONLINE_DAYS_PARAM="^[0-9]+$" + +BACKUP_LEVEL0_DAYS="${BACKUP_LEVEL0_DAYS:-0}" +BACKUP_LEVEL0_DAYS_PARAM="^[0-6]-?[0-6]?$" + +BACKUP_LEVEL1_DAYS="${BACKUP_LEVEL1_DAYS:-1-6}" +BACKUP_LEVEL1_DAYS_PARAM="^[0-6]-?[0-6]?$" + +BACKUP_START_HOUR="${BACKUP_START_HOUR:-01}" +BACKUP_START_HOUR_PARAM="^(2[0-3]|[01]?[0-9])$" + +BACKUP_START_MIN="${BACKUP_START_MIN:-00}" +BACKUP_START_MIN_PARAM="^[0-5][0-9]$" + +ARCHIVE_BACKUP_MIN="${ARCHIVE_BACKUP_MIN:-30}" +ARCHIVE_BACKUP_MIN_PARAM="^[0-5][0-9]$" + +BACKUP_SCRIPT_LOCATION="${BACKUP_SCRIPT_LOCATION:-/home/oracle/scripts}" +BACKUP_SCRIPT_LOCATION_PARAM="^/.+$" + +BACKUP_LOG_LOCATION="${BACKUP_LOG_LOCATION:-/home/oracle/logs}" +BACKUP_LOG_LOCATION_PARAM="^/.+$" + +export ANSIBLE_DISPLAY_SKIPPED_HOSTS=false + +### +GETOPT_MANDATORY="instance-ip-addr:" +GETOPT_OPTIONAL="ora-db-name:,ora-db-domain:,ora-db-charset:,ora-db-ncharset:,ora-db-container:,ora-db-type:" +GETOPT_OPTIONAL="$GETOPT_OPTIONAL,ora-db-home-dir:,ora-pdb-name-prefix:,ora-pdb-count:,ora-redo-log-size:" +GETOPT_OPTIONAL="$GETOPT_OPTIONAL,ora-data-destination:,ora-reco-destination:,ora-listener-port:,ora-listener-name:" +GETOPT_OPTIONAL="$GETOPT_OPTIONAL,backup-dest:,backup-redundancy:,archive-redundancy:,archive-online-days:,backup-level0-days:,backup-level1-days:" +GETOPT_OPTIONAL="$GETOPT_OPTIONAL,backup-start-hour:,backup-start-min:,archive-backup-min:,backup-script-location:,backup-log-location:" +GETOPT_OPTIONAL="$GETOPT_OPTIONAL,instance-ssh-user:,instance-ssh-key:,instance-hostname:,instance-ssh-extra-args:" +GETOPT_OPTIONAL="$GETOPT_OPTIONAL,sga-target:,pga-aggregate-target:,memory-percent:" +GETOPT_OPTIONAL="$GETOPT_OPTIONAL,help,validate,check-instance,config-db,debug" +GETOPT_LONG="$GETOPT_MANDATORY,$GETOPT_OPTIONAL" +GETOPT_SHORT="h" + +VALIDATE=0 + +options="$(getopt --longoptions "$GETOPT_LONG" --options "$GETOPT_SHORT" -- "$@")" + +[ $? -eq 0 ] || { + echo "Invalid options provided: $@" >&2 + exit 1 +} + +eval set -- "$options" + +while true; do + case "$1" in + --ora-db-name) + ORA_DB_NAME="$2" + shift + ;; + --ora-db-domain) + ORA_DB_DOMAIN="$2" + shift + ;; + --ora-db-charset) + ORA_DB_CHARSET="$2" + shift + ;; + --ora-db-ncharset) + ORA_DB_NCHARSET="$2" + shift + ;; + --ora-db-container) + ORA_DB_CONTAINER="$2" + shift + ;; + --ora-db-home-dir) + ORA_DB_HOME_DIR="$2" + shift + ;; + --ora-db-type) + ORA_DB_TYPE="$2" + shift + ;; + --ora-pdb-name-prefix) + ORA_PDB_NAME_PREFIX="$2" + shift + ;; + --ora-pdb-count) + ORA_PDB_COUNT="$2" + shift + ;; + --ora-redo-log-size) + ORA_REDO_LOG_SIZE="$2" + shift + ;; + --ora-data-destination) + ORA_DATA_DESTINATION="$2" + shift + ;; + --ora-reco-destination) + ORA_RECO_DESTINATION="$2" + shift + ;; + --ora-listener-port) + ORA_LISTENER_PORT="$2" + shift + ;; + --ora-listener-name) + ORA_LISTENER_NAME="$2" + shift + ;; + --backup-dest) + BACKUP_DEST="$2" + shift + ;; + --backup-redundancy) + BACKUP_REDUNDANCY="$2" + shift + ;; + --archive-redundancy) + ARCHIVE_REDUNDANCY="$2" + shift + ;; + --archive-online-days) + ARCHIVE_ONLINE_DAYS="$2" + shift + ;; + --backup-level0-days) + BACKUP_LEVEL0_DAYS="$2" + shift + ;; + --backup-level1-days) + BACKUP_LEVEL1_DAYS="$2" + shift + ;; + --backup-start-hour) + BACKUP_START_HOUR="$2" + shift + ;; + --backup-start-min) + BACKUP_START_MIN="$2" + shift + ;; + --archive-backup-min) + ARCHIVE_BACKUP_MIN="$2" + shift + ;; + --backup-script-location) + BACKUP_SCRIPT_LOCATION="$2" + shift + ;; + --backup-log-location) + BACKUP_LOG_LOCATION="$2" + shift + ;; + --sga-target) + SGA_TARGET="$2" + shift + ;; + --pga-aggregate-target) + PGA_AGGREGATE_TARGET="$2" + shift + ;; + --memory-percent) + MEMORY_PERCENT="$2" + shift + ;; + --instance-ip-addr) + INSTANCE_IP_ADDR="$2" + shift + ;; + --instance-ssh-key) + INSTANCE_SSH_KEY="$2" + shift + ;; + --instance-hostname) + INSTANCE_HOSTNAME="$2" + shift + ;; + --instance-ssh-user) + INSTANCE_SSH_USER="$2" + shift + ;; + --instance-ssh-extra-args) + INSTANCE_SSH_EXTRA_ARGS="$2" + shift + ;; + --check-instance) + PARAM_PB_CHECK_INSTANCE="${PB_CHECK_INSTANCE}" + ;; + --config-db) + PARAM_PB_CONFIG_DB="${PB_CONFIG_DB}" + ;; + --debug) + export ANSIBLE_DEBUG=1 + export ANSIBLE_DISPLAY_SKIPPED_HOSTS=true + ;; + --validate) + VALIDATE=1 + ;; + --help | -h) + echo -e "\tUsage: $(basename $0)" >&2 + echo "${GETOPT_MANDATORY}" | sed 's/,/\n/g' | sed 's/:/ /' | sed 's/\(.\+\)/\t --\1/' + echo "${GETOPT_OPTIONAL}" | sed 's/,/\n/g' | sed 's/:/ /' | sed 's/\(.\+\)/\t [ --\1 ]/' + echo -e "\t -- [parameters sent to ansible]" + exit 2 + ;; + --) + shift + break + ;; + esac + shift +done + +# +# Build the playbook list to execute depending on the command line option if specified +# +# Only modify PB_LIST if specific playbooks are requested via parameters +if [[ -n "${PARAM_PB_CHECK_INSTANCE}" || -n "${PARAM_PB_CONFIG_DB}" ]]; then + PARAM_PB_LIST="" + for PARAM in "${PARAM_PB_CHECK_INSTANCE}" "${PARAM_PB_CONFIG_DB}"; do + if [[ -n "${PARAM}" ]]; then + PARAM_PB_LIST="${PARAM_PB_LIST} ${PARAM}" + fi + done + if [[ -n "${PARAM_PB_LIST}" ]]; then + PB_LIST="${PARAM_PB_LIST}" + fi +fi + +# Print the playbooks that will be executed +printf "\n\033[1;36m%s\033[m\n\n" "Will execute the following playbooks: ${PB_LIST}" + +# +# Parameter defaults +# +INSTANCE_HOSTNAME="${INSTANCE_HOSTNAME:-$INSTANCE_IP_ADDR}" + +# +# Variables verification +# +shopt -s nocasematch + +[[ ! "$ORA_DB_NAME" =~ $ORA_DB_NAME_PARAM ]] && { + echo "Incorrect parameter provided for ora-db-name: $ORA_DB_NAME" + exit 1 +} +[[ ! "$ORA_DB_DOMAIN" =~ $ORA_DB_DOMAIN_PARAM ]] && { + echo "Incorrect parameter provided for ora-db-domain: $ORA_DB_DOMAIN" + exit 1 +} +[[ ! "$ORA_DB_CHARSET" =~ $ORA_DB_CHARSET_PARAM ]] && { + echo "Incorrect parameter provided for ora-db-charset: $ORA_DB_CHARSET" + exit 1 +} +[[ ! "$ORA_DB_NCHARSET" =~ $ORA_DB_NCHARSET_PARAM ]] && { + echo "Incorrect parameter provided for ora-db-ncharset: $ORA_DB_NCHARSET" + exit 1 +} +[[ ! "$ORA_DB_CONTAINER" =~ $ORA_DB_CONTAINER_PARAM ]] && { + echo "Incorrect parameter provided for ora-db-container: $ORA_DB_CONTAINER" + exit 1 +} +[[ ! "$ORA_DB_TYPE" =~ $ORA_DB_TYPE_PARAM ]] && { + echo "Incorrect parameter provided for ora-db-type: $ORA_DB_TYPE" + exit 1 +} +[[ -n "$ORA_DB_HOME_DIR" && ! "$ORA_DB_HOME_DIR" =~ $ORA_DB_HOME_DIR_PARAM ]] && { + echo "Incorrect parameter provided for ora-db-home-dir: $ORA_DB_HOME_DIR" + exit 1 +} +[[ ! "$ORA_PDB_NAME_PREFIX" =~ $ORA_PDB_NAME_PREFIX_PARAM ]] && { + echo "Incorrect parameter provided for ora-pdb-name-prefix: $ORA_PDB_NAME_PREFIX" + exit 1 +} +[[ ! "$ORA_PDB_COUNT" =~ $ORA_PDB_COUNT_PARAM ]] && { + echo "Incorrect parameter provided for ora-pdb-count: $ORA_PDB_COUNT" + exit 1 +} +[[ ! "$ORA_REDO_LOG_SIZE" =~ $ORA_REDO_LOG_SIZE_PARAM ]] && { + echo "Incorrect parameter provided for ora-redo-log-size: $ORA_REDO_LOG_SIZE" + exit 1 +} +[[ ! "$ORA_DATA_DESTINATION" =~ $ORA_DATA_DESTINATION_PARAM ]] && { + echo "Incorrect parameter provided for ora-data-destination: $ORA_DATA_DESTINATION" + exit 1 +} +[[ ! "$ORA_RECO_DESTINATION" =~ $ORA_RECO_DESTINATION_PARAM ]] && { + echo "Incorrect parameter provided for ora-reco-destination: $ORA_RECO_DESTINATION" + exit 1 +} +[[ ! "$ORA_LISTENER_PORT" =~ $ORA_LISTENER_PORT_PARAM ]] && { + echo "Incorrect parameter provided for ora-listener-port: $ORA_LISTENER_PORT" + exit 1 +} +[[ ! "$ORA_LISTENER_NAME" =~ $ORA_LISTENER_NAME_PARAM ]] && { + echo "Incorrect parameter provided for ora-listener-name: $ORA_LISTENER_NAME" + exit 1 +} +[[ -n "$SGA_TARGET" && ! "$SGA_TARGET" =~ $SGA_TARGET_PARAM ]] && { + echo "Incorrect parameter provided for sga-target: $SGA_TARGET" + exit 1 +} +[[ -n "$PGA_AGGREGATE_TARGET" && ! "$PGA_AGGREGATE_TARGET" =~ $PGA_AGGREGATE_TARGET_PARAM ]] && { + echo "Incorrect parameter provided for pga-aggregate-target: $PGA_AGGREGATE_TARGET" + exit 1 +} +[[ -n "$MEMORY_PERCENT" && ! "$MEMORY_PERCENT" =~ $MEMORY_PERCENT_PARAM ]] && { + echo "Incorrect parameter provided for memory-percent: $MEMORY_PERCENT" + exit 1 +} +[[ ! "$BACKUP_DEST" =~ $BACKUP_DEST_PARAM ]] && [[ "$PB_LIST" =~ "config-db.yml" ]] && { + echo "Incorrect parameter provided for backup-dest: $BACKUP_DEST" + exit 1 +} +[[ ! "$BACKUP_REDUNDANCY" =~ $BACKUP_REDUNDANCY_PARAM ]] && { + echo "Incorrect parameter provided for backup-redundancy: $BACKUP_REDUNDANCY" + exit 1 +} +[[ ! "$ARCHIVE_REDUNDANCY" =~ $ARCHIVE_REDUNDANCY_PARAM ]] && { + echo "Incorrect parameter provided for archive-redundancy: $ARCHIVE_REDUNDANCY" + exit 1 +} +[[ ! "$ARCHIVE_ONLINE_DAYS" =~ $ARCHIVE_ONLINE_DAYS_PARAM ]] && { + echo "Incorrect parameter provided for archive-online-days: $ARCHIVE_ONLINE_DAYS" + exit 1 +} +[[ ! "$BACKUP_LEVEL0_DAYS" =~ $BACKUP_LEVEL0_DAYS_PARAM ]] && { + echo "Incorrect parameter provided for backup-level0-days: $BACKUP_LEVEL0_DAYS" + exit 1 +} +[[ ! "$BACKUP_LEVEL1_DAYS" =~ $BACKUP_LEVEL1_DAYS_PARAM ]] && { + echo "Incorrect parameter provided for backup-level1-days: $BACKUP_LEVEL1_DAYS" + exit 1 +} +[[ ! "$BACKUP_START_HOUR" =~ $BACKUP_START_HOUR_PARAM ]] && { + echo "Incorrect parameter provided for backup-start-hour: $BACKUP_START_HOUR" + exit 1 +} +[[ ! "$BACKUP_START_MIN" =~ $BACKUP_START_MIN_PARAM ]] && { + echo "Incorrect parameter provided for backup-start-min: $BACKUP_START_MIN" + exit 1 +} +[[ ! "$ARCHIVE_BACKUP_MIN" =~ $ARCHIVE_BACKUP_MIN_PARAM ]] && { + echo "Incorrect parameter provided for archive-backup-min: $ARCHIVE_BACKUP_MIN" + exit 1 +} +[[ ! "$BACKUP_SCRIPT_LOCATION" =~ $BACKUP_SCRIPT_LOCATION_PARAM ]] && { + echo "Incorrect parameter provided for backup-script-location: $BACKUP_SCRIPT_LOCATION" + exit 1 +} +[[ ! "$BACKUP_LOG_LOCATION" =~ $BACKUP_LOG_LOCATION_PARAM ]] && { + echo "Incorrect parameter provided for backup-log-location: $BACKUP_LOG_LOCATION" + exit 1 +} +[[ ! "$INSTANCE_IP_ADDR" =~ ${INSTANCE_IP_ADDR_PARAM} ]] && { + echo "Incorrect parameter provided for instance-ip-addr: $INSTANCE_IP_ADDR" + exit 1 +} +[[ ! "$INSTANCE_SSH_USER" =~ $INSTANCE_SSH_USER_PARAM ]] && { + echo "Incorrect parameter provided for instance-ssh-user: $INSTANCE_SSH_USER" + exit 1 +} +[[ ! "$INSTANCE_SSH_KEY" =~ $INSTANCE_SSH_KEY_PARAM ]] && { + echo "Incorrect parameter provided for instance-ssh-key: $INSTANCE_SSH_KEY" + exit 1 +} + +# Oracle home existence check +if [[ -n "$ORA_DB_HOME_DIR" ]]; then + printf "\n\033[1;36m%s\033[m\n\n" "Checking Oracle Home directory: $ORA_DB_HOME_DIR" + + # Build the inventory file for checking + INVENTORY_FILE_CHECK="${INVENTORY_FILE}_check_${TIMESTAMP}" + COMMON_OPTIONS="ansible_ssh_user=${INSTANCE_SSH_USER} ansible_ssh_private_key_file=${INSTANCE_SSH_KEY} ansible_ssh_extra_args=${INSTANCE_SSH_EXTRA_ARGS}" + + cat <"${INVENTORY_FILE_CHECK}" +[${INSTANCE_HOSTGROUP_NAME}] +${INSTANCE_HOSTNAME} ansible_ssh_host=${INSTANCE_IP_ADDR} ${COMMON_OPTIONS} +EOF + + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_CHECK} -m stat -a 'path=${ORA_DB_HOME_DIR}'" + eval "${ANSIBLE_COMMAND}" > /dev/null + + if [ $? -ne 0 ]; then + printf "\n\033[1;31m%s\033[m\n\n" "The specified Oracle Home directory ${ORA_DB_HOME_DIR} does not exist or is not accessible." + exit 1 + fi + + # Validate Oracle binaries + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_CHECK} -m stat -a 'path=${ORA_DB_HOME_DIR}/bin/sqlplus'" + eval "${ANSIBLE_COMMAND}" > /dev/null + + if [ $? -ne 0 ]; then + printf "\n\033[1;31m%s\033[m\n\n" "The specified Oracle Home directory ${ORA_DB_HOME_DIR} does not contain valid Oracle binaries (sqlplus not found)." + exit 1 + fi + + # Check inventory.xml + ORACLE_INVENTORY_DIR="/etc/oraInst.loc" + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_CHECK} -m shell -a 'if [ -f ${ORACLE_INVENTORY_DIR} ]; then cat ${ORACLE_INVENTORY_DIR} | grep inventory_loc | cut -d= -f2; else echo \"/u01/app/oraInventory\"; fi'" + INVENTORY_LOC=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$" | tr -d '[:space:]') + + if [ -z "$INVENTORY_LOC" ]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: Could not determine Oracle inventory location. Will proceed, but inventory registration should be verified manually." + else + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_CHECK} -m shell -a 'grep -l ${ORA_DB_HOME_DIR} ${INVENTORY_LOC}/ContentsXML/inventory.xml || echo \"NOT_FOUND\"'" + INVENTORY_RESULT=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$") + + if [[ "$INVENTORY_RESULT" == *"NOT_FOUND"* ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: The specified Oracle Home directory ${ORA_DB_HOME_DIR} is not registered in the Oracle inventory." + printf "\n\033[1;33m%s\033[m\n\n" "This might cause issues. Please verify that this Oracle home was properly installed." + fi + fi + + # Database existence check + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_CHECK} -m shell -a 'ps -ef | grep pmon | grep -i ${ORA_DB_NAME} | grep -v grep || echo \"NOT_RUNNING\"'" + DB_RUNNING=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$") + + if [[ "$DB_RUNNING" != *"NOT_RUNNING"* ]]; then + printf "\n\033[1;31m%s\033[m\n\n" "A database process with name ${ORA_DB_NAME} is already running on the target system." + exit 1 + fi + + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_CHECK} -m shell -a 'grep -i \"${ORA_DB_NAME}:\" /etc/oratab || echo \"NOT_FOUND\"'" + DB_ORATAB=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$") + + if [[ "$DB_ORATAB" != *"NOT_FOUND"* ]]; then + printf "\n\033[1;31m%s\033[m\n\n" "A database with name ${ORA_DB_NAME} is already registered in /etc/oratab on the target system." + exit 1 + fi + + # Get Oracle version from specified home - using multiple patterns to handle different version formats + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_CHECK} -m shell -a 'export ORACLE_HOME=${ORA_DB_HOME_DIR}; ${ORA_DB_HOME_DIR}/bin/sqlplus -V'" + SQLPLUS_VERSION_OUTPUT=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$") + + # Try different regex patterns for version extraction + ORACLE_VERSION=$(echo "$SQLPLUS_VERSION_OUTPUT" | grep -o "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" | head -1) + + if [[ -z "$ORACLE_VERSION" ]]; then + # Try alternative pattern for older versions + ORACLE_VERSION=$(echo "$SQLPLUS_VERSION_OUTPUT" | grep -o "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" | head -1) + + if [[ -n "$ORACLE_VERSION" ]]; then + # Convert to 5-part version if needed + if [[ "$ORACLE_VERSION" == "11.2.0" ]]; then + ORACLE_VERSION="11.2.0.4.0" + elif [[ "$ORACLE_VERSION" == "12.1.0" ]]; then + ORACLE_VERSION="12.1.0.2.0" + elif [[ "$ORACLE_VERSION" == "12.2.0" ]]; then + ORACLE_VERSION="12.2.0.1.0" + elif [[ "$ORACLE_VERSION" == "18.0.0" || "$ORACLE_VERSION" == "18.3.0" ]]; then + ORACLE_VERSION="18.0.0.0.0" + elif [[ "$ORACLE_VERSION" == "19.0.0" || "$ORACLE_VERSION" == "19.3.0" ]]; then + ORACLE_VERSION="19.3.0.0.0" + else + # Append missing parts + ORACLE_VERSION="${ORACLE_VERSION}.0.0" + fi + fi + fi + + if [[ -z "$ORACLE_VERSION" ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: Could not determine Oracle version from the specified home. Will use default values." + ORACLE_VERSION="19.3.0.0.0" + fi + + printf "\n\033[1;36m%s\033[m\n\n" "Detected Oracle version: ${ORACLE_VERSION}" + ORA_VERSION="${ORACLE_VERSION}" + + # Clean up temporary inventory file + rm -f "${INVENTORY_FILE_CHECK}" +fi + +# Memory calculations +if [[ -n "$MEMORY_PERCENT" ]]; then + # Get total memory from target system + INVENTORY_FILE_MEM="${INVENTORY_FILE}_mem_${TIMESTAMP}" + COMMON_OPTIONS="ansible_ssh_user=${INSTANCE_SSH_USER} ansible_ssh_private_key_file=${INSTANCE_SSH_KEY} ansible_ssh_extra_args=${INSTANCE_SSH_EXTRA_ARGS}" + + cat <"${INVENTORY_FILE_MEM}" +[${INSTANCE_HOSTGROUP_NAME}] +${INSTANCE_HOSTNAME} ansible_ssh_host=${INSTANCE_IP_ADDR} ${COMMON_OPTIONS} +EOF + + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_MEM} -m shell -a 'free -m | grep Mem | awk \"{print \\\$2}\"'" + TOTAL_MEMORY_MB=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$" | tr -d '[:space:]') + + if [[ -z "$TOTAL_MEMORY_MB" || ! "$TOTAL_MEMORY_MB" =~ ^[0-9]+$ ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: Could not determine total memory. Will use default memory settings." + # Default to 2GB SGA for safety + SGA_TARGET="2048M" + PGA_AGGREGATE_TARGET="512M" + else + # Calculate SGA_TARGET based on percentage + SGA_TARGET_MB=$((TOTAL_MEMORY_MB * MEMORY_PERCENT / 100)) + + # Ensure minimum SGA size (1GB) + if [ $SGA_TARGET_MB -lt 1024 ]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: Calculated SGA size is less than 1GB. Setting to minimum 1GB." + SGA_TARGET_MB=1024 + fi + + SGA_TARGET="${SGA_TARGET_MB}M" + + # Default PGA to 150M or 25% of SGA, whichever is greater + PGA_DEFAULT_MB=$((SGA_TARGET_MB / 4)) + if [ $PGA_DEFAULT_MB -lt 150 ]; then + PGA_DEFAULT_MB=150 + fi + + if [[ -z "$PGA_AGGREGATE_TARGET" ]]; then + PGA_AGGREGATE_TARGET="${PGA_DEFAULT_MB}M" + fi + + printf "\n\033[1;36m%s\033[m\n\n" "Memory settings based on ${MEMORY_PERCENT}% of total memory (${TOTAL_MEMORY_MB}MB):" + printf "\n\033[1;36m%s\033[m\n\n" "SGA_TARGET: ${SGA_TARGET}" + printf "\n\033[1;36m%s\033[m\n\n" "PGA_AGGREGATE_TARGET: ${PGA_AGGREGATE_TARGET}" + fi + + # Clean up temporary inventory file + rm -f "${INVENTORY_FILE_MEM}" +elif [[ -z "$SGA_TARGET" && -z "$PGA_AGGREGATE_TARGET" ]]; then + # No memory settings provided, use sensible defaults + printf "\n\033[1;33m%s\033[m\n\n" "No memory settings provided. Will use Ansible defaults (45% of total memory for SGA, 150M for PGA)." +fi + +# +# Build the inventory file +# +INVENTORY_FILE="${INVENTORY_FILE}_${INSTANCE_HOSTNAME}_${ORA_DB_NAME}_${TIMESTAMP}" +COMMON_OPTIONS="ansible_ssh_user=${INSTANCE_SSH_USER} ansible_ssh_private_key_file=${INSTANCE_SSH_KEY} ansible_ssh_extra_args=${INSTANCE_SSH_EXTRA_ARGS}" + +cat <"${INVENTORY_FILE}" +[${INSTANCE_HOSTGROUP_NAME}] +${INSTANCE_HOSTNAME} ansible_ssh_host=${INSTANCE_IP_ADDR} ${COMMON_OPTIONS} +EOF + +if [[ -f "${INVENTORY_FILE}" ]]; then + printf "\n\033[1;36m%s\033[m\n\n" "Inventory file for this execution: ${INVENTORY_FILE}." +else + printf "\n\033[1;31m%s\033[m\n\n" "Cannot find the inventory file ${INVENTORY_FILE}; cannot continue." + exit 124 +fi + +# +# Build the logfile for this session +# +LOG_FILE="${LOG_FILE}_${INSTANCE_HOSTNAME}_${ORA_DB_NAME}_${TIMESTAMP}.log" +export ANSIBLE_LOG_PATH=${LOG_FILE} + +# +# Trim tailing slashes from variables with paths +# +BACKUP_DEST=${BACKUP_DEST%/} +BACKUP_LOG_LOCATION=${BACKUP_LOG_LOCATION%/} +BACKUP_SCRIPT_LOCATION=${BACKUP_SCRIPT_LOCATION%/} + +if [[ -n "$ORA_DB_HOME_DIR" ]]; then + ORA_DB_HOME_DIR=${ORA_DB_HOME_DIR%/} +fi + +# Validate backup destination exists and is writable before proceeding +if [[ -n "${BACKUP_DEST}" ]]; then + INVENTORY_FILE_BACKUP="${INVENTORY_FILE}_backup_check_${TIMESTAMP}" + COMMON_OPTIONS="ansible_ssh_user=${INSTANCE_SSH_USER} ansible_ssh_private_key_file=${INSTANCE_SSH_KEY} ansible_ssh_extra_args=${INSTANCE_SSH_EXTRA_ARGS}" + + cat <"${INVENTORY_FILE_BACKUP}" +[${INSTANCE_HOSTGROUP_NAME}] +${INSTANCE_HOSTNAME} ansible_ssh_host=${INSTANCE_IP_ADDR} ${COMMON_OPTIONS} +EOF + + # Check backup destination + printf "\n\033[1;36m%s\033[m\n\n" "Checking backup destination: ${BACKUP_DEST}" + + # Handle backup destination based on its type + if [[ "${BACKUP_DEST:0:1}" == "/" ]]; then + # Filesystem destination + printf "\n\033[1;36m%s\033[m\n\n" "Filesystem path specified for backup: ${BACKUP_DEST}" + + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_BACKUP} -m shell -a 'if [ -d \"${BACKUP_DEST}\" ]; then echo \"EXISTS\"; else echo \"NOT_EXISTS\"; fi'" + BACKUP_DIR_CHECK=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$") + + if [[ "${BACKUP_DIR_CHECK}" == *"NOT_EXISTS"* ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: Backup destination ${BACKUP_DEST} does not exist. Attempting to create it." + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_BACKUP} -m file -a 'path=${BACKUP_DEST} state=directory owner=oracle group=oinstall mode=0755' -b" + eval "${ANSIBLE_COMMAND}" > /dev/null + + if [ $? -ne 0 ]; then + printf "\n\033[1;31m%s\033[m\n\n" "Failed to create backup destination directory ${BACKUP_DEST}. Please check permissions and path." + exit 1 + else + printf "\n\033[1;36m%s\033[m\n\n" "Successfully created backup destination directory ${BACKUP_DEST}." + fi + fi + elif [[ "${BACKUP_DEST:0:1}" == "+" ]]; then + # ASM destination with + prefix + printf "\n\033[1;36m%s\033[m\n\n" "ASM disk group specified for backup: ${BACKUP_DEST}" + + # Verify the ASM disk group exists + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_BACKUP} -m shell -a 'export ORACLE_HOME={{ oracle_home }}; export ORACLE_SID=+ASM; echo \"set heading off\\nset feedback off\\nselect name from v\\\$asm_diskgroup where name=UPPER(\\\"${BACKUP_DEST:1}\\\");\\nexit;\" | $ORACLE_HOME/bin/sqlplus -s / as sysasm || echo \"ASM_NOT_RUNNING\"' -b --become-user=grid" + DISKGROUP_CHECK=$(eval "${ANSIBLE_COMMAND}" 2>/dev/null | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$" || echo "ASM_ERROR") + + if [[ "${DISKGROUP_CHECK}" == *"ASM_NOT_RUNNING"* || "${DISKGROUP_CHECK}" == *"ASM_ERROR"* ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: Could not verify ASM disk group ${BACKUP_DEST}. ASM may not be running or not accessible." + printf "\n\033[1;33m%s\033[m\n\n" "Please ensure the disk group exists before running backups." + elif [[ -z "${DISKGROUP_CHECK}" ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: ASM disk group ${BACKUP_DEST:1} does not appear to exist." + printf "\n\033[1;33m%s\033[m\n\n" "Please ensure the disk group exists before running backups." + else + printf "\n\033[1;36m%s\033[m\n\n" "ASM disk group ${BACKUP_DEST:1} verified." + fi + else + # ASM destination without + prefix + printf "\n\033[1;36m%s\033[m\n\n" "ASM disk group specified for backup: ${BACKUP_DEST} (will be used as +${BACKUP_DEST})" + + # Verify the ASM disk group exists + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_BACKUP} -m shell -a 'export ORACLE_HOME={{ oracle_home }}; export ORACLE_SID=+ASM; echo \"set heading off\\nset feedback off\\nselect name from v\\\$asm_diskgroup where name=UPPER(\\\"${BACKUP_DEST}\\\");\\nexit;\" | $ORACLE_HOME/bin/sqlplus -s / as sysasm || echo \"ASM_NOT_RUNNING\"' -b --become-user=grid" + DISKGROUP_CHECK=$(eval "${ANSIBLE_COMMAND}" 2>/dev/null | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$" || echo "ASM_ERROR") + + if [[ "${DISKGROUP_CHECK}" == *"ASM_NOT_RUNNING"* || "${DISKGROUP_CHECK}" == *"ASM_ERROR"* ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: Could not verify ASM disk group ${BACKUP_DEST}. ASM may not be running or not accessible." + printf "\n\033[1;33m%s\033[m\n\n" "Please ensure the disk group exists before running backups." + elif [[ -z "${DISKGROUP_CHECK}" ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: ASM disk group ${BACKUP_DEST} does not appear to exist." + printf "\n\033[1;33m%s\033[m\n\n" "Please ensure the disk group exists before running backups." + else + printf "\n\033[1;36m%s\033[m\n\n" "ASM disk group ${BACKUP_DEST} verified." + fi + fi + + # Check RECO destination + printf "\n\033[1;36m%s\033[m\n\n" "Checking recovery area destination: ${ORA_RECO_DESTINATION}" + + # Handle recovery area destination based on its type + if [[ "${ORA_RECO_DESTINATION:0:1}" == "/" ]]; then + # Filesystem destination + printf "\n\033[1;36m%s\033[m\n\n" "Filesystem path specified for recovery area: ${ORA_RECO_DESTINATION}" + + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_BACKUP} -m shell -a 'if [ -d \"${ORA_RECO_DESTINATION}\" ]; then echo \"EXISTS\"; else echo \"NOT_EXISTS\"; fi'" + RECO_DIR_CHECK=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$") + + if [[ "${RECO_DIR_CHECK}" == *"NOT_EXISTS"* ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: Recovery area destination ${ORA_RECO_DESTINATION} does not exist. Attempting to create it." + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_BACKUP} -m file -a 'path=${ORA_RECO_DESTINATION} state=directory owner=oracle group=oinstall mode=0755' -b" + eval "${ANSIBLE_COMMAND}" > /dev/null + + if [ $? -ne 0 ]; then + printf "\n\033[1;31m%s\033[m\n\n" "Failed to create recovery area directory ${ORA_RECO_DESTINATION}. Please check permissions and path." + exit 1 + else + printf "\n\033[1;36m%s\033[m\n\n" "Successfully created recovery area directory ${ORA_RECO_DESTINATION}." + fi + fi + elif [[ "${ORA_RECO_DESTINATION:0:1}" == "+" ]]; then + # ASM destination with + prefix + printf "\n\033[1;36m%s\033[m\n\n" "ASM disk group specified for recovery area: ${ORA_RECO_DESTINATION}" + + # Verify the ASM disk group exists + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_BACKUP} -m shell -a 'export ORACLE_HOME={{ oracle_home }}; export ORACLE_SID=+ASM; echo \"set heading off\\nset feedback off\\nselect name from v\\\$asm_diskgroup where name=UPPER(\\\"${ORA_RECO_DESTINATION:1}\\\");\\nexit;\" | $ORACLE_HOME/bin/sqlplus -s / as sysasm || echo \"ASM_NOT_RUNNING\"' -b --become-user=grid" + DISKGROUP_CHECK=$(eval "${ANSIBLE_COMMAND}" 2>/dev/null | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$" || echo "ASM_ERROR") + + if [[ "${DISKGROUP_CHECK}" == *"ASM_NOT_RUNNING"* || "${DISKGROUP_CHECK}" == *"ASM_ERROR"* ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: Could not verify ASM disk group ${ORA_RECO_DESTINATION}. ASM may not be running or not accessible." + printf "\n\033[1;33m%s\033[m\n\n" "Please ensure the disk group exists before proceeding." + elif [[ -z "${DISKGROUP_CHECK}" ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: ASM disk group ${ORA_RECO_DESTINATION:1} does not appear to exist." + printf "\n\033[1;33m%s\033[m\n\n" "Please ensure the disk group exists before proceeding." + else + printf "\n\033[1;36m%s\033[m\n\n" "ASM disk group ${ORA_RECO_DESTINATION:1} verified." + fi + else + # ASM destination without + prefix + printf "\n\033[1;36m%s\033[m\n\n" "ASM disk group specified for recovery area: ${ORA_RECO_DESTINATION} (will be used as +${ORA_RECO_DESTINATION})" + + # Verify the ASM disk group exists + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_BACKUP} -m shell -a 'export ORACLE_HOME={{ oracle_home }}; export ORACLE_SID=+ASM; echo \"set heading off\\nset feedback off\\nselect name from v\\\$asm_diskgroup where name=UPPER(\\\"${ORA_RECO_DESTINATION}\\\");\\nexit;\" | $ORACLE_HOME/bin/sqlplus -s / as sysasm || echo \"ASM_NOT_RUNNING\"' -b --become-user=grid" + DISKGROUP_CHECK=$(eval "${ANSIBLE_COMMAND}" 2>/dev/null | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$" || echo "ASM_ERROR") + + if [[ "${DISKGROUP_CHECK}" == *"ASM_NOT_RUNNING"* || "${DISKGROUP_CHECK}" == *"ASM_ERROR"* ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: Could not verify ASM disk group ${ORA_RECO_DESTINATION}. ASM may not be running or not accessible." + printf "\n\033[1;33m%s\033[m\n\n" "Please ensure the disk group exists before proceeding." + elif [[ -z "${DISKGROUP_CHECK}" ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: ASM disk group ${ORA_RECO_DESTINATION} does not appear to exist." + printf "\n\033[1;33m%s\033[m\n\n" "Please ensure the disk group exists before proceeding." + else + printf "\n\033[1;36m%s\033[m\n\n" "ASM disk group ${ORA_RECO_DESTINATION} verified." + fi + fi + + # Check if oracle user can write to the backup destination if it's a filesystem path + if [[ "${BACKUP_DEST:0:1}" == "/" ]]; then + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_BACKUP} -m shell -a 'touch ${BACKUP_DEST}/test_$RANDOM; if [ $? -eq 0 ]; then rm ${BACKUP_DEST}/test_$RANDOM; echo \"WRITABLE\"; else echo \"NOT_WRITABLE\"; fi' -b --become-user=oracle" + BACKUP_DIR_WRITABLE=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$") + + if [[ "${BACKUP_DIR_WRITABLE}" == *"NOT_WRITABLE"* ]]; then + printf "\n\033[1;31m%s\033[m\n\n" "Backup destination ${BACKUP_DEST} is not writable by the Oracle user. Please check permissions." + exit 1 + fi + fi + + # Check if oracle user can write to the RECO destination if it's a filesystem path + if [[ "${ORA_RECO_DESTINATION:0:1}" == "/" ]]; then + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_BACKUP} -m shell -a 'touch ${ORA_RECO_DESTINATION}/test_$RANDOM; if [ $? -eq 0 ]; then rm ${ORA_RECO_DESTINATION}/test_$RANDOM; echo \"WRITABLE\"; else echo \"NOT_WRITABLE\"; fi' -b --become-user=oracle" + RECO_DIR_WRITABLE=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$") + + if [[ "${RECO_DIR_WRITABLE}" == *"NOT_WRITABLE"* ]]; then + printf "\n\033[1;31m%s\033[m\n\n" "Recovery area destination ${ORA_RECO_DESTINATION} is not writable by the Oracle user. Please check permissions." + exit 1 + fi + fi + + # Clean up temporary inventory file + rm -f "${INVENTORY_FILE_BACKUP}" +fi + +# Validate memory settings +if [[ -n "${SGA_TARGET}" ]]; then + # Extract numeric value and unit + SGA_SIZE=$(echo "${SGA_TARGET}" | grep -o '[0-9]\+') + SGA_UNIT=$(echo "${SGA_TARGET}" | grep -o '[MG%]') + + # Check if SGA size is too small (less than 500M) + if [[ "${SGA_UNIT}" == "M" && ${SGA_SIZE} -lt 500 ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: SGA_TARGET value ${SGA_TARGET} is very small. Oracle recommends at least 500M for production databases." + elif [[ "${SGA_UNIT}" == "G" && ${SGA_SIZE} -lt 1 ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: SGA_TARGET value ${SGA_TARGET} is very small. Oracle recommends at least 1G for production databases." + fi + + # Convert to GB for calculations - M to GB divide by 1024, G stays as is + if [[ "${SGA_UNIT}" == "M" ]]; then + # Calculate with simple bash arithmetic (integer division, less precise but doesn't require bc) + SGA_SIZE_GB=$(( ${SGA_SIZE} / 1024 )) + # Add decimal part for better accuracy + if [[ ${SGA_SIZE} -lt 1024 ]]; then + SGA_SIZE_GB="0.$(( (${SGA_SIZE} * 100) / 1024 ))" + fi + elif [[ "${SGA_UNIT}" == "G" ]]; then + SGA_SIZE_GB=${SGA_SIZE} + else + SGA_SIZE_GB=2 # Default value for percentage or unrecognized units + fi +fi + +if [[ -n "${PGA_AGGREGATE_TARGET}" ]]; then + # Extract numeric value and unit + PGA_SIZE=$(echo "${PGA_AGGREGATE_TARGET}" | grep -o '[0-9]\+') + PGA_UNIT=$(echo "${PGA_AGGREGATE_TARGET}" | grep -o '[MG%]') + + # Check if PGA size is too small (less than 150M) + if [[ "${PGA_UNIT}" == "M" && ${PGA_SIZE} -lt 150 ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: PGA_AGGREGATE_TARGET value ${PGA_AGGREGATE_TARGET} is very small. Oracle recommends at least 150M." + elif [[ "${PGA_UNIT}" == "G" && ${PGA_SIZE} -lt 1 ]]; then + printf "\n\033[1;33m%s\033[m\n\n" "Warning: PGA_AGGREGATE_TARGET value ${PGA_AGGREGATE_TARGET} is very small." + fi + + # Convert to GB for calculations - M to GB divide by 1024, G stays as is + if [[ "${PGA_UNIT}" == "M" ]]; then + # Calculate with simple bash arithmetic (integer division, less precise but doesn't require bc) + PGA_SIZE_GB=$(( ${PGA_SIZE} / 1024 )) + # Add decimal part for better accuracy + if [[ ${PGA_SIZE} -lt 1024 ]]; then + PGA_SIZE_GB="0.$(( (${PGA_SIZE} * 100) / 1024 ))" + fi + elif [[ "${PGA_UNIT}" == "G" ]]; then + PGA_SIZE_GB=${PGA_SIZE} + else + PGA_SIZE_GB="0.5" # Default value for percentage or unrecognized units + fi +fi + +# Calculate total required memory in GB using pure bash +# First, ensure the variables are defined with defaults +SGA_SIZE_GB=${SGA_SIZE_GB:-2} +PGA_SIZE_GB=${PGA_SIZE_GB:-0.5} + +# Convert to integers with 2 decimal places (multiply by 100) +SGA_INT=$(echo $SGA_SIZE_GB | awk '{printf "%.0f", $1*100}') +PGA_INT=$(echo $PGA_SIZE_GB | awk '{printf "%.0f", $1*100}') + +# Add the integers and convert back to decimal +TOTAL_INT=$((SGA_INT + PGA_INT)) +REQUIRED_MEMORY_GB=$(echo $TOTAL_INT | awk '{printf "%.1f", $1/100}') + +# Validate memory against target machine early +if [[ -n "${INSTANCE_IP_ADDR}" ]]; then + INVENTORY_FILE_RAM="${INVENTORY_FILE}_ram_check_${TIMESTAMP}" + COMMON_OPTIONS="ansible_ssh_user=${INSTANCE_SSH_USER} ansible_ssh_private_key_file=${INSTANCE_SSH_KEY} ansible_ssh_extra_args=${INSTANCE_SSH_EXTRA_ARGS}" + + cat <"${INVENTORY_FILE_RAM}" +[${INSTANCE_HOSTGROUP_NAME}] +${INSTANCE_HOSTNAME} ansible_ssh_host=${INSTANCE_IP_ADDR} ${COMMON_OPTIONS} +EOF + + printf "\n\033[1;36m%s\033[m\n\n" "Checking available RAM on target system..." + + # Check both total and available memory + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_RAM} -m shell -a 'free -g | grep Mem | awk \"{print \\\$2, \\\$4}\"'" + MEMORY_CHECK=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$") + + if [[ -n "${MEMORY_CHECK}" && "${MEMORY_CHECK}" =~ [0-9]+[[:space:]][0-9]+ ]]; then + TOTAL_MEMORY_GB=$(echo ${MEMORY_CHECK} | awk '{print $1}') + AVAILABLE_MEMORY_GB=$(echo ${MEMORY_CHECK} | awk '{print $2}') + + printf "\n\033[1;36m%s\033[m\n\n" "Memory check results:" + printf "\n\033[1;36m%s\033[m\n\n" "Total system memory: ${TOTAL_MEMORY_GB}GB" + printf "\n\033[1;36m%s\033[m\n\n" "Available memory: ${AVAILABLE_MEMORY_GB}GB" + printf "\n\033[1;36m%s\033[m\n\n" "Required memory (SGA + PGA): ${REQUIRED_MEMORY_GB}GB" + + # Check if available memory is sufficient - convert to integer for comparison + REQUIRED_INT=$(echo $REQUIRED_MEMORY_GB | awk '{printf "%.0f", $1}') + if (( AVAILABLE_MEMORY_GB < REQUIRED_INT )); then + printf "\n\033[1;31m%s\033[m\n\n" "ERROR: Not enough available memory on the target system." + printf "\n\033[1;31m%s\033[m\n\n" "Required memory (SGA + PGA): ${REQUIRED_MEMORY_GB}GB" + printf "\n\033[1;31m%s\033[m\n\n" "Available memory: ${AVAILABLE_MEMORY_GB}GB" + printf "\n\033[1;31m%s\033[m\n\n" "Please reduce SGA_TARGET and/or PGA_AGGREGATE_TARGET values to fit within available memory or free up memory on the target system." + rm -f "${INVENTORY_FILE_RAM}" + exit 1 + fi + else + printf "\n\033[1;33m%s\033[m\n\n" "Warning: Could not determine available memory on target system. Will continue, but may encounter memory issues during database creation." + fi + + # Check for database existence early + printf "\n\033[1;36m%s\033[m\n\n" "Checking if database ${ORA_DB_NAME} already exists on target system..." + + # Check oratab for database + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_RAM} -m shell -a 'grep -i \"${ORA_DB_NAME}:\" /etc/oratab || echo \"NOT_FOUND\"'" + DB_ORATAB=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$") + + # Check for running process + ANSIBLE_COMMAND="ansible ${INSTANCE_HOSTGROUP_NAME} -i ${INVENTORY_FILE_RAM} -m shell -a 'ps -ef | grep pmon | grep -i ${ORA_DB_NAME} | grep -v grep || echo \"NOT_FOUND\"'" + DB_RUNNING=$(eval "${ANSIBLE_COMMAND}" | grep -v "CHANGED" | grep -v "SUCCESS" | grep -v "^$") + + if [[ "${DB_ORATAB}" != *"NOT_FOUND"* ]]; then + printf "\n\033[1;31m%s\033[m\n\n" "ERROR: A database with name ${ORA_DB_NAME} is already registered in /etc/oratab on the target system." + printf "\n\033[1;31m%s\033[m\n\n" "Please choose a different database name or remove the existing database." + rm -f "${INVENTORY_FILE_RAM}" + exit 1 + fi + + if [[ "${DB_RUNNING}" != *"NOT_FOUND"* ]]; then + printf "\n\033[1;31m%s\033[m\n\n" "ERROR: A database process with name ${ORA_DB_NAME} is already running on the target system." + printf "\n\033[1;31m%s\033[m\n\n" "Please choose a different database name or stop the existing database." + rm -f "${INVENTORY_FILE_RAM}" + exit 1 + fi + + printf "\n\033[1;36m%s\033[m\n\n" "Database existence check passed. No database named ${ORA_DB_NAME} exists on the target system." + rm -f "${INVENTORY_FILE_RAM}" +fi + +# Set appropriate DB container flag for Ansible +if [[ "${ORA_DB_CONTAINER}" == "TRUE" ]]; then + DB_CONTAINER_FLAG="true" +else + DB_CONTAINER_FLAG="false" +fi + +# Ensure swlib_unzip_path is set - fallback to temp directory if not specified +if [[ -z "${ORA_STAGING}" ]]; then + ORA_STAGING="/tmp/oracle_swlib" + printf "\n\033[1;33m%s\033[m\n\n" "Warning: ORA_STAGING not set. Using temporary directory ${ORA_STAGING} for staging files." + # Create the staging directory if it doesn't exist + mkdir -p "${ORA_STAGING}" 2>/dev/null +fi + +# Export all variables including those for Ansible mappings +export ARCHIVE_BACKUP_MIN +export ARCHIVE_ONLINE_DAYS +export ARCHIVE_REDUNDANCY +export BACKUP_DEST +export BACKUP_LEVEL0_DAYS +export BACKUP_LEVEL1_DAYS +export BACKUP_LOG_LOCATION +export BACKUP_REDUNDANCY +export BACKUP_START_HOUR +export BACKUP_START_MIN +export BACKUP_SCRIPT_LOCATION +export DB_CONTAINER_FLAG +export INSTANCE_IP_ADDR +export ORA_DATA_DESTINATION +export ORA_DB_CHARSET +export ORA_DB_CONTAINER +export ORA_DB_DOMAIN +export ORA_DB_NAME +export ORA_DB_NCHARSET +export ORA_DB_TYPE +export ORA_DB_HOME_DIR +export ORA_LISTENER_NAME +export ORA_LISTENER_PORT +export ORA_PDB_COUNT +export ORA_PDB_NAME_PREFIX +export ORA_RECO_DESTINATION +export ORA_REDO_LOG_SIZE +export ORA_STAGING +export ORA_VERSION +export PB_LIST +export SGA_TARGET +export PGA_AGGREGATE_TARGET +export MEMORY_PERCENT +export CALLING_SCRIPT="add-database.sh" +# Export the standard Ansible variable names for clarity +export container_db="${DB_CONTAINER_FLAG}" +export pdb_prefix="${ORA_PDB_NAME_PREFIX}" +export db_name="${ORA_DB_NAME}" +export db_domain="${ORA_DB_DOMAIN}" +export scripts_dir="${BACKUP_SCRIPT_LOCATION}" +export logs_dir="${BACKUP_LOG_LOCATION}" +export swlib_unzip_path="${ORA_STAGING}" + +# CREATE_LISTENER will be determined by playbook based on whether a listener exists +# We don't set it here, allowing the playbook to decide whether to create one or not +export CREATE_LISTENER=auto + +# Always create database +export CREATE_DB=true + +echo -e "Running with parameters from command line or environment variables:\n" +set | grep -E '^(ORA_|BACKUP_|ARCHIVE_|INSTANCE_|PB_|ANSIBLE_|CREATE_|SGA_|PGA_|MEMORY_)' | grep -v '_PARAM=' +echo + +# Display a validation summary before proceeding +printf "\n\033[1;36m%s\033[m\n" "======= PRE-FLIGHT VALIDATION SUMMARY =======" +printf "\033[1;36m%s\033[m\n" "Database Name: ${ORA_DB_NAME}" +printf "\033[1;36m%s\033[m\n" "Target System: ${INSTANCE_IP_ADDR} (${INSTANCE_HOSTNAME})" + +if [[ -n "$ORA_DB_HOME_DIR" ]]; then + printf "\033[1;36m%s\033[m\n" "Oracle Home: ${ORA_DB_HOME_DIR}" +else + printf "\033[1;33m%s\033[m\n" "Oracle Home: Auto-detect (will be determined during execution)" +fi + +printf "\033[1;36m%s\033[m\n" "Memory Settings:" +printf "\033[1;36m%s\033[m\n" " - SGA Target: ${SGA_TARGET}" +printf "\033[1;36m%s\033[m\n" " - PGA Aggregate Target: ${PGA_AGGREGATE_TARGET}" +printf "\033[1;36m%s\033[m\n" " - Required Memory: ${REQUIRED_MEMORY_GB}GB" + +printf "\033[1;36m%s\033[m\n" "Container Database: ${ORA_DB_CONTAINER}" +if [[ "${ORA_DB_CONTAINER}" == "TRUE" ]]; then + printf "\033[1;36m%s\033[m\n" " - PDB Count: ${ORA_PDB_COUNT}" + printf "\033[1;36m%s\033[m\n" " - PDB Prefix: ${ORA_PDB_NAME_PREFIX}" +fi + +if [[ -n "${BACKUP_DEST}" ]]; then + printf "\033[1;36m%s\033[m\n" "Backup Configuration:" + printf "\033[1;36m%s\033[m\n" " - Backup Destination: ${BACKUP_DEST}" + printf "\033[1;36m%s\033[m\n" " - Backup Redundancy: ${BACKUP_REDUNDANCY}" + printf "\033[1;36m%s\033[m\n" " - Archive Redundancy: ${ARCHIVE_REDUNDANCY}" +fi + +printf "\033[1;36m%s\033[m\n\n" "=============================================" + +ANSIBLE_PARAMS="-i ${INVENTORY_FILE} ${ANSIBLE_PARAMS}" +ANSIBLE_CMDLINE_PARAMS="${*}" + +# Build extra parameters with proper escaping +build_extra_param() { + local param_name="$1" + local param_value="$2" + + if [[ -n "$param_value" ]]; then + # Properly escape the value for Ansible + param_value=$(printf '%s' "$param_value" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g') + echo "-e $param_name=\"$param_value\"" + else + echo "" + fi +} + +# Reset the extra params +ANSIBLE_EXTRA_PARAMS="" + +# Add all required parameters with proper escaping +if [[ -n "$ORA_DB_HOME_DIR" ]]; then + ANSIBLE_EXTRA_PARAMS="${ANSIBLE_EXTRA_PARAMS} $(build_extra_param "oracle_home" "${ORA_DB_HOME_DIR}")" +fi + +if [[ -n "$SGA_TARGET" ]]; then + ANSIBLE_EXTRA_PARAMS="${ANSIBLE_EXTRA_PARAMS} $(build_extra_param "sga_target" "${SGA_TARGET}")" +fi + +if [[ -n "$PGA_AGGREGATE_TARGET" ]]; then + ANSIBLE_EXTRA_PARAMS="${ANSIBLE_EXTRA_PARAMS} $(build_extra_param "pga_aggtar" "${PGA_AGGREGATE_TARGET}")" +fi + +if [[ -n "$MEMORY_PERCENT" ]]; then + ANSIBLE_EXTRA_PARAMS="${ANSIBLE_EXTRA_PARAMS} $(build_extra_param "memory_pct" "${MEMORY_PERCENT}")" +fi + +# Add container database flag +ANSIBLE_EXTRA_PARAMS="${ANSIBLE_EXTRA_PARAMS} $(build_extra_param "db_container_flag" "${DB_CONTAINER_FLAG}")" + +# Add database name and domain +ANSIBLE_EXTRA_PARAMS="${ANSIBLE_EXTRA_PARAMS} $(build_extra_param "db_name" "${ORA_DB_NAME}")" +ANSIBLE_EXTRA_PARAMS="${ANSIBLE_EXTRA_PARAMS} $(build_extra_param "db_domain" "${ORA_DB_DOMAIN}")" + +# Add backup directories +if [[ -n "$BACKUP_SCRIPT_LOCATION" ]]; then + ANSIBLE_EXTRA_PARAMS="${ANSIBLE_EXTRA_PARAMS} $(build_extra_param "scripts_dir" "${BACKUP_SCRIPT_LOCATION}")" +fi + +if [[ -n "$BACKUP_LOG_LOCATION" ]]; then + ANSIBLE_EXTRA_PARAMS="${ANSIBLE_EXTRA_PARAMS} $(build_extra_param "logs_dir" "${BACKUP_LOG_LOCATION}")" +fi + +# Add software library path +ANSIBLE_EXTRA_PARAMS="${ANSIBLE_EXTRA_PARAMS} $(build_extra_param "swlib_unzip_path" "${ORA_STAGING}")" + +echo "Ansible params: ${ANSIBLE_EXTRA_PARAMS}" + +if [ $VALIDATE -eq 1 ]; then + echo "Exiting because of --validate" + exit +fi + +export ANSIBLE_NOCOWS=1 + +ANSIBLE_PLAYBOOK="ansible-playbook" +if ! type ansible-playbook >/dev/null 2>&1; then + echo "Ansible executable not found in path" + exit 3 +else + echo "Found Ansible: $(type ansible-playbook)" +fi + +# exit on any error from the following scripts +set -e + +for PLAYBOOK in ${PB_LIST}; do + ANSIBLE_COMMAND="${ANSIBLE_PLAYBOOK} ${ANSIBLE_PARAMS} ${ANSIBLE_CMDLINE_PARAMS} ${ANSIBLE_EXTRA_PARAMS} ${PLAYBOOK}" + echo + echo "Running Ansible playbook: ${ANSIBLE_COMMAND}" + eval "${ANSIBLE_COMMAND}" +done + +# +# Function to cleanup all temporary files +# +cleanup_temp_files() { + # Store any temporary inventory files created during the run + local temp_files=() + temp_files+=("${INVENTORY_FILE_CHECK:-}") + temp_files+=("${INVENTORY_FILE_MEM:-}") + temp_files+=("${INVENTORY_FILE_BACKUP:-}") + temp_files+=("${INVENTORY_FILE_RAM:-}") + + # Clean up temporary files + for file in "${temp_files[@]}"; do + if [[ -n "${file}" && -f "${file}" ]]; then + rm -f "${file}" + fi + done + + # Also clean up any temporary files that match our naming pattern + # This ensures we don't leave any files behind even if the script is interrupted + find "${INVENTORY_DIR}" -name "inventory_*_${TIMESTAMP}*" -type f -delete 2>/dev/null || true +} + +# +# Run cleanup before exit +# +cleanup_temp_files + +# +# Show the files used by this session +# +printf "\n\033[1;36m%s\033[m\n" "Files used by this session:" +for FILE in "${INVENTORY_FILE}" "${LOG_FILE}"; do + if [[ -f "${FILE}" ]]; then + printf "\t\033[1;36m- %s\033[m\n" "${FILE}" + fi +done +printf "\n" + +exit 0 \ No newline at end of file diff --git a/cleanup-oracle.sh b/cleanup-oracle.sh index 35c28da49..0c9db6e72 100755 --- a/cleanup-oracle.sh +++ b/cleanup-oracle.sh @@ -13,6 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Set calling script name for validation tasks +export CALLING_SCRIPT="cleanup-oracle.sh" + echo Command used: echo "$0 $@" echo diff --git a/config-db.yml b/config-db.yml index 69903053f..53a13ec8b 100644 --- a/config-db.yml +++ b/config-db.yml @@ -23,7 +23,85 @@ that: "ansible_version.full is version_compare('2.8', '>=')" fail_msg: "You must update Ansible to at least 2.8 to use these playbooks" success_msg: "Ansible version is {{ ansible_version.full }}, continuing" + + - name: Ensure swlib directory exists + file: + path: "{{ swlib_unzip_path }}" + state: directory + owner: "{{ oracle_user | default('oracle') }}" + group: "{{ oracle_group | default('oinstall') }}" + mode: "0755" + become: true + tags: always + + # Include common role for Oracle Home validation + - name: Include common tasks + include_role: + name: common + tasks_from: validate-oracle-home + tags: always + + # Memory settings are now handled centrally in group_vars/all.yml + - name: Display memory settings + debug: + msg: "Using memory settings: SGA_TARGET={{ sga_target }}, PGA_AGGREGATE_TARGET={{ pga_aggtar }}, MEMORY_PERCENT={{ memory_pct }}%" + tags: always + + # Retrieve create_listener variable value + - name: Check if listener creation is required + set_fact: + create_listener: "{{ lookup('env', 'CREATE_LISTENER')|lower|default('false') }}" + tags: always + + # Set container_db from environment if provided + - name: Set container_db from environment + set_fact: + db_container_flag: "{{ lookup('env', 'DB_CONTAINER_FLAG')|lower|default('false') }}" + tags: always + tasks: + - name: Check for listener service on configured port + shell: | + netstat -lntp | grep -w {{ listener_port | default('1521') }} || true + become: true + register: listener_check + changed_when: false + failed_when: false + tags: lsnr-check + + - name: Check listener status with lsnrctl + become: true + become_user: "{{ oracle_user }}" + shell: | + export ORACLE_HOME={{ oracle_home }} + export PATH=$ORACLE_HOME/bin:$PATH + lsnrctl status | grep -i "listening on" || echo "NO_LISTENER" + register: lsnrctl_check + changed_when: false + failed_when: false + tags: lsnr-check + + - name: Check if listener exists + set_fact: + listener_exists: "{{ listener_check.stdout != '' or lsnrctl_check.stdout != 'NO_LISTENER' }}" + tags: lsnr-check + + - name: Display listener status + debug: + msg: > + {% if listener_exists %} + Listener appears to be running. New database will register with existing listener. + {% else %} + No listener detected. Will create one automatically. + {% endif %} + tags: lsnr-check + + # Set create_listener to true if listener doesn't exist + - name: Set listener creation flag + set_fact: + create_listener: "{{ not listener_exists }}" + tags: lsnr-check + - include_role: name: lsnr-create when: create_listener | bool @@ -61,3 +139,31 @@ when: - cluster_type == "DG" tags: dg-create + + - name: Configure control file autobackup format + shell: | + export ORACLE_HOME={{ oracle_home }} + export PATH=$ORACLE_HOME/bin:$PATH + + # Standardize the recovery destination format for control file autobackup + if [[ "{{ reco_destination }}" == "/"* ]]; then + # Filesystem path - no prefix needed + RECO_DEST_FINAL="{{ reco_destination }}" + else + # ASM destination - add + if not already present + if [[ "{{ reco_destination }}" == "+"* ]]; then + RECO_DEST_FINAL="{{ reco_destination }}" + else + RECO_DEST_FINAL="+{{ reco_destination }}" + fi + fi + + # Always ensure %F format specifier is included for both filesystem and ASM + AUTOBACKUP_FORMAT="${RECO_DEST_FINAL}/{{ oracle_sid }}/%F" + + $ORACLE_HOME/bin/sqlplus / as sysdba < Run with the Ansible debugging flag enabled. + +Oracle home directory (Required) +

+ORA_DB_HOME_DIR
+--ora-db-home-dir
+

+Required parameter - no default +Absolute path to an existing Oracle home directory. +Required parameter for add-database.sh script. + + +Skip listener configuration +

+--skip-listener-config
+

+ +Skip listener configuration when adding a new database. +Only available in add-database.sh script. + + +SGA target setting +

+SGA_TARGET
+--sga-target
+

+user defined value with suffix M/G or percentage% +Explicit SGA target value or percentage of system memory. +Only available in add-database.sh script. + + +PGA aggregate target setting +

+PGA_AGGREGATE_TARGET
+--pga-aggregate-target
+

+user defined value with suffix M/G or percentage% +Explicit PGA aggregate target value or percentage of system memory. +Only available in add-database.sh script. + + +Memory percentage +

+MEMORY_PERCENT
+--memory-percent
+

+user defined integer (1-100) +Percentage of system memory to allocate to database SGA. +Only available in add-database.sh script. + @@ -2363,6 +2430,226 @@ $ ./install-oracle.sh --ora-version=7.3.4 --ora-swlib-bucket gs://oracle-softwar Incorrect parameter provided for ora-version: 7.3.4 ``` +## Adding More Databases to Existing Oracle Installations + +The `add-database.sh` script allows you to create and configure additional databases on servers where Oracle software has already been installed. This is useful for creating multiple databases that utilize the same Oracle home directory or for adding databases after the initial setup. + +### Key Features + +- Create multiple databases on a single Oracle installation +- Configure container databases with pluggable databases (PDBs) +- Set up memory parameters (SGA, PGA) for optimal performance +- Configure backup and recovery destinations +- Customize database character sets and other database parameters + +### Command Syntax + +```bash +./add-database.sh --instance-ip-addr --ora-db-home-dir [optional parameters] +``` + +### Required Parameters + +| Parameter | Description | +|-----------|-------------| +| `--instance-ip-addr` | IP address of the target Oracle server | +| `--ora-db-home-dir` | Absolute path to an existing Oracle home directory | + +### Common Optional Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `--ora-db-name` | Name of the database to create | ORCL | +| `--backup-dest` | Backup destination location | (required) | +| `--ora-reco-destination` | Recovery area destination | RECO | + +### Memory Configuration Options + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `--sga-target` | Specific SGA memory allocation | (calculated) | +| `--pga-aggregate-target` | Specific PGA memory allocation | 150M | +| `--memory-percent` | Percentage of system memory to allocate | 45 | + +### Container Database Options + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `--ora-db-container` | Create as container database (TRUE/FALSE) | FALSE | +| `--ora-pdb-name-prefix` | Prefix for PDB names | PDB | +| `--ora-pdb-count` | Number of PDBs to create | 1 | + +### Listener Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `--skip-listener-config` | Skip listener configuration | (not set) | +| `--ora-listener-port` | Listener port number | 1521 | +| `--ora-listener-name` | Listener name | LISTENER | + +### Example Usage Scenarios + +1. **Adding a database with explicit memory settings:** + + ```bash + ./add-database.sh \ + --instance-ip-addr ${INSTANCE_IP_ADDR} \ + --ora-db-name SALES \ + --ora-db-home-dir /u01/app/oracle/product/19.0.0/dbhome_1 \ + --sga-target 4G \ + --pga-aggregate-target 1G \ + --backup-dest "+RECO" + ``` + +2. **Adding a container database with multiple PDBs:** + + ```bash + ./add-database.sh \ + --instance-ip-addr ${INSTANCE_IP_ADDR} \ + --ora-db-name FINANCE \ + --ora-db-home-dir /u01/app/oracle/product/19.0.0/dbhome_1 \ + --memory-percent 30 \ + --ora-db-container TRUE \ + --ora-pdb-name-prefix FIN \ + --ora-pdb-count 3 \ + --backup-dest "+RECO" + ``` + +3. **Adding a database with filesystem backup and recovery areas:** + + ```bash + ./add-database.sh \ + --instance-ip-addr ${INSTANCE_IP_ADDR} \ + --ora-db-name HR \ + --ora-db-home-dir /u01/app/oracle/product/19.0.0/dbhome_1 \ + --backup-dest "/u02/backups" \ + --ora-reco-destination "/u03/recovery" + ``` + +4. **Adding a database and reusing an existing listener:** + + ```bash + ./add-database.sh \ + --instance-ip-addr ${INSTANCE_IP_ADDR} \ + --ora-db-name REPORTING \ + --ora-db-home-dir /u01/app/oracle/product/19.0.0/dbhome_1 \ + --skip-listener-config \ + --backup-dest "+RECO" + ``` + +To see all available options, run: + +```bash +./add-database.sh --help +``` + +### Notes + +- The `--backup-dest` parameter is required and can be either an ASM disk group (e.g., "+RECO") or a filesystem path. +- If using a filesystem path for `--ora-reco-destination`, ensure the oracle user has write permissions to the specified directory. +- When specifying memory settings, you can either set explicit values with `--sga-target` and `--pga-aggregate-target` or use `--memory-percent` to allocate a percentage of the system memory. +- For production deployments, it's recommended to explicitly specify the Oracle home directory with `--ora-db-home-dir` rather than relying on auto-detection. + +### Oracle Home Existence Checks + +Before creating a new database, the script performs several validation checks on the specified Oracle home: + +1. Verifies that the Oracle home directory exists +2. Confirms that the Oracle binaries (oracle) are present +3. Checks if the Oracle home is registered in the inventory.xml file +4. Detects the Oracle version from the specified home + +These checks help prevent errors and ensure that you're using a valid Oracle installation. + +### Database Existence Checks + +To prevent accidental overwrites of existing databases, the script performs these checks: + +1. Searches for running processes with the specified database name +2. Verifies against /etc/oratab entries for existing databases + +If a database with the same name is found, the script will abort with a clear error message. + +### Memory Configuration + +The script provides flexible memory configuration options: + +1. Explicit memory settings: + - Use `--sga-target` to specify SGA size (e.g., "4G", "4096M") + - Use `--pga-aggregate-target` to specify PGA size + +2. Percentage-based allocation: + - Use `--memory-percent` to allocate a percentage of total system memory + - The script automatically calculates appropriate SGA and PGA values + +The script also implements memory validation checks to ensure your system has sufficient resources. + +### Example Add-Database Script Execution + +```bash +$ ./add-database.sh --instance-ip-addr 10.0.0.5 --ora-db-name SALES --ora-db-home-dir /u01/app/oracle/product/19.0.0/dbhome_1 --memory-percent 45 --backup-dest "+RECO" + +Checking Oracle Home directory: /u01/app/oracle/product/19.0.0/dbhome_1 + +Detected Oracle version: 19.3.0.0.0 + +Memory settings based on 45% of total memory (32768MB): +SGA_TARGET: 14745M +PGA_AGGREGATE_TARGET: 3686M + +Inventory file for this execution: ./inventory_files/inventory_oraclehost_SALES_20240301_120145. + +Running with parameters from command line or environment variables: + +ANSIBLE_LOG_PATH=./logs/log_oraclehost_SALES_20240301_120145.log +ARCHIVE_BACKUP_MIN=30 +ARCHIVE_ONLINE_DAYS=7 +ARCHIVE_REDUNDANCY=2 +BACKUP_DEST=+RECO +BACKUP_LEVEL0_DAYS=0 +BACKUP_LEVEL1_DAYS=1-6 +BACKUP_LOG_LOCATION=/home/oracle/logs +BACKUP_REDUNDANCY=2 +BACKUP_SCRIPT_LOCATION=/home/oracle/scripts +BACKUP_START_HOUR=01 +BACKUP_START_MIN=00 +CREATE_DB=true +CREATE_LISTENER=true +INSTANCE_HOSTNAME=oraclehost +INSTANCE_IP_ADDR=10.0.0.5 +INSTANCE_SSH_EXTRA_ARGS='-o ServerAliveInterval=60 -o ServerAliveCountMax=3 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentityAgent=no' +INSTANCE_SSH_KEY=~/.ssh/id_rsa +INSTANCE_SSH_USER=oracle-admin +MEMORY_PERCENT=45 +ORA_DATA_DESTINATION=DATA +ORA_DB_CHARSET=AL32UTF8 +ORA_DB_CONTAINER=TRUE +ORA_DB_DOMAIN= +ORA_DB_HOME_DIR=/u01/app/oracle/product/19.0.0/dbhome_1 +ORA_DB_NAME=SALES +ORA_DB_NCHARSET=AL16UTF16 +ORA_DB_TYPE=MULTIPURPOSE +ORA_LISTENER_NAME=LISTENER +ORA_LISTENER_PORT=1521 +ORA_PDB_COUNT=1 +ORA_PDB_NAME_PREFIX=PDB +ORA_RECO_DESTINATION=RECO +ORA_REDO_LOG_SIZE=100MB +ORA_VERSION=19.3.0.0.0 +PB_CHECK_INSTANCE=check-instance.yml +PB_CONFIG_DB=config-db.yml +PB_LIST=check-instance.yml config-db.yml +PGA_AGGREGATE_TARGET=3686M +SGA_TARGET=14745M + +Ansible params: -e oracle_home=/u01/app/oracle/product/19.0.0/dbhome_1 -e sga_target=14745M -e pga_aggtar=3686M -e memory_pct=45 +Found Ansible: ansible-playbook is /usr/bin/ansible-playbook + +Running Ansible playbook: ansible-playbook -i ./inventory_files/inventory_oraclehost_SALES_20240301_120145 -e oracle_home=/u01/app/oracle/product/19.0.0/dbhome_1 -e sga_target=14745M -e pga_aggtar=3686M -e memory_pct=45 check-instance.yml + +... output truncated for brevity ... +``` + ## Oracle Database Free Edition Specific Details and Changes The toolkit supports the installation of the Oracle Database "Free Edition", availble for download from [Oracle Database Free Get Started](https://www.oracle.com/database/free/get-started/). diff --git a/group_vars/all.yml b/group_vars/all.yml index 3eb96b476..20d592ba6 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -112,9 +112,18 @@ arch_bu_start_min: "{{ lookup('env','ARCHIVE_BACKUP_MIN')|default('30',true) }}" # DB configuration related variables: db_config_type: "{% if lookup('env','CLUSTER_TYPE')|default('NONE', true) in ('NONE', 'DG') %}SI{% elif lookup('env','CLUSTER_TYPE') == 'RAC' %}RAC{% endif %}" instance_num: 1 -memory_pct: 45 -sga_target: "{{ (ansible_memory_mb.real.total*memory_pct)//100 }}M" -pga_aggtar: 150M + +# Memory settings with environment variable overrides +memory_pct: "{{ lookup('env', 'MEMORY_PERCENT') | default('45', true) | int }}" +# Reduced memory percentage when using --config-db to create a new database +db_memory_pct: "{{ lookup('env', 'DB_MEMORY_PERCENT') | default('25', true) | int }}" +# Use the appropriate memory percent based on whether we're running with --config-db +effective_memory_pct: "{% if lookup('env', 'CALLING_SCRIPT') == 'install-oracle.sh' and lookup('env', 'PARAM_PB_CONFIG_DB') is defined and lookup('env', 'PARAM_PB_CONFIG_DB') != '' %}{{ db_memory_pct }}{% else %}{{ memory_pct }}{% endif %}" +# Convert memory calculation to MB first to avoid integer truncation +sga_target_default: "{{ ((ansible_memory_mb.real.total | int) * (effective_memory_pct | int) / 100) | round | int }}M" +sga_target: "{{ lookup('env', 'SGA_TARGET') | default(sga_target_default, true) }}" +pga_aggtar_default: "{{ ((ansible_memory_mb.real.total | int) * 5 / 100) | round | int }}M" +pga_aggtar: "{{ lookup('env', 'PGA_AGGREGATE_TARGET') | default(pga_aggtar_default, true) }}" oracle_user: oracle oracle_group: oinstall grid_user: "{% if role_separation|bool %}grid{% else %}{{ oracle_user }}{% endif %}" diff --git a/install-oracle.sh b/install-oracle.sh index 2b25c8843..ab4d845c0 100755 --- a/install-oracle.sh +++ b/install-oracle.sh @@ -952,6 +952,7 @@ export ORA_RELEASE export PB_LIST export PRIMARY_IP_ADDR export SWAP_BLK_DEVICE +export CALLING_SCRIPT="install-oracle.sh" echo -e "Running with parameters from command line or environment variables:\n" set | grep -E '^(ORA_|BACKUP_|ARCHIVE_|INSTANCE_|PB_|ANSIBLE_|CLUSTER|PRIMARY)' | grep -v '_PARAM=' diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml new file mode 100644 index 000000000..95269ea20 --- /dev/null +++ b/roles/common/tasks/main.yml @@ -0,0 +1,38 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +# Include tasks for common operations +- name: Include variable population tasks + include_tasks: populate-vars.yml + tags: always + +- name: Include Oracle Home validation + include_tasks: validate-oracle-home.yml + tags: always + +- name: Include ASM disks population + include_tasks: populate-asm-disks.yml + when: asm_disks is defined + tags: asm + +- name: Include swap partition ID population + include_tasks: populate-swap-partition-id.yml + when: swap_blk_device is defined + tags: swap + +- name: Include user data mounts population + include_tasks: populate-user-data-mounts.yml + when: data_mounts is defined + tags: data-mounts \ No newline at end of file diff --git a/roles/common/tasks/validate-oracle-home.yml b/roles/common/tasks/validate-oracle-home.yml new file mode 100644 index 000000000..967f9ab1d --- /dev/null +++ b/roles/common/tasks/validate-oracle-home.yml @@ -0,0 +1,182 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +# Get Oracle Home from the --ora-db-home-dir parameter or auto-detect +- name: Determine the calling script + set_fact: + calling_script: "{{ lookup('env', 'CALLING_SCRIPT') | default('unknown', true) }}" + tags: always + +- name: Check for Oracle homes in /etc/oratab + shell: | + if [ -f /etc/oratab ]; then + grep -v "^#" /etc/oratab | grep -v "^$" | grep -v "^+ASM" | + awk -F: '{print $2}' | + xargs -I{} bash -c 'HOME="{}"; if [ -x "$HOME/bin/oracle" ]; then echo "$HOME"; fi' | head -1 + fi + register: oratab_first_valid_home + failed_when: false + changed_when: false + when: + - lookup('env', 'ORA_DB_HOME_DIR') is not defined or lookup('env', 'ORA_DB_HOME_DIR')|length == 0 + - calling_script == 'add-database.sh' + tags: always + +- name: Set Oracle Home from /etc/oratab if available and parameter not provided + set_fact: + oracle_home: "{{ oratab_first_valid_home.stdout }}" + when: + - lookup('env', 'ORA_DB_HOME_DIR') is not defined or lookup('env', 'ORA_DB_HOME_DIR')|length == 0 + - calling_script == 'add-database.sh' + - oratab_first_valid_home.stdout is defined + - oratab_first_valid_home.stdout|length > 0 + tags: always + +- name: Verify Oracle Home parameter was provided for add-database.sh if no valid homes in oratab + fail: + msg: | + The --ora-db-home-dir parameter is required when using add-database.sh. + Please specify the path to an existing Oracle Database home. + Example: --ora-db-home-dir=/u01/app/oracle/product/21.3.0/dbhome_1 + when: + - (lookup('env', 'ORA_DB_HOME_DIR') is not defined or lookup('env', 'ORA_DB_HOME_DIR')|length == 0) + - (oratab_first_valid_home.stdout is not defined or oratab_first_valid_home.stdout|length == 0) + - calling_script == 'add-database.sh' or lookup('env', 'ORACLE_HOME_REQUIRED') | default('false') | bool + tags: always + +- name: Set Oracle Home from parameter if provided + set_fact: + oracle_home: "{{ lookup('env', 'ORA_DB_HOME_DIR') }}" + when: lookup('env', 'ORA_DB_HOME_DIR') is defined and lookup('env', 'ORA_DB_HOME_DIR')|length > 0 + tags: always + +- name: Set default Oracle Home path according to best practices (when not provided) + set_fact: + oracle_home: "{{ oracle_root }}/{{ oracle_user }}/product/{{ oracle_ver_base }}/{{ home_name }}" + when: + - lookup('env', 'ORA_DB_HOME_DIR') is not defined or lookup('env', 'ORA_DB_HOME_DIR')|length == 0 + - calling_script != 'add-database.sh' and (lookup('env', 'ORACLE_HOME_REQUIRED') | default('false') | bool == false) + tags: always + +# Basic validation of Oracle home only when required (for add-database.sh) +- name: Check if Oracle home exists (with .0 suffix) + stat: + path: "{{ oracle_home }}/bin/oracle" + register: oracle_binary + failed_when: false + when: calling_script != "install-oracle.sh" + tags: always + +- name: Check if Oracle home exists (without .0 suffix) + stat: + path: "{{ oracle_home | regex_replace('\\.[0-9]+(?=/dbhome)', '') }}/bin/oracle" + register: oracle_binary_alt + failed_when: false + when: + - calling_script != "install-oracle.sh" + - not oracle_binary.stat.exists | default(false) | bool + - oracle_home is match('.*\\.[0-9]+/dbhome.*') + tags: always + +- name: Update Oracle home path if alternative path exists + set_fact: + oracle_home: "{{ oracle_home | regex_replace('\\.[0-9]+(?=/dbhome)', '') }}" + when: + - not oracle_binary.stat.exists | default(false) | bool + - oracle_binary_alt.stat is defined + - oracle_binary_alt.stat.exists | default(false) | bool + tags: always + +- name: Check oratab entries for valid homes + shell: | + ORATAB_HOMES="" + if [ -f /etc/oratab ]; then + ORATAB_HOMES=$(grep -v "^#" /etc/oratab | grep -v "^$" | grep -v "^+ASM" | + awk -F: '{print $2 " (SID: " $1 ")"}' | + xargs -I{} bash -c 'HOME=$(echo "{}" | cut -d" " -f1); if [ -x "$HOME/bin/oracle" ]; then echo " - {}"; fi') + fi + + if [ -n "$ORATAB_HOMES" ]; then + echo "Available Oracle Database homes in /etc/oratab:" + echo "$ORATAB_HOMES" + else + echo "No valid Oracle Database homes found in /etc/oratab." + fi + register: oratab_homes_list + failed_when: false + changed_when: false + when: + - calling_script != "install-oracle.sh" + - not oracle_binary.stat.exists is defined or not oracle_binary.stat.exists + tags: always + +- name: Detect Oracle version from specified Oracle home + block: + - name: Get version from installed Oracle binary + shell: | + ORACLE_HOME_PATH="{{ oracle_home }}" + if [ -x "${ORACLE_HOME_PATH}/bin/oracle" ]; then + VERSION=$(strings ${ORACLE_HOME_PATH}/bin/oracle | grep -E "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" | head -1) + if [ -n "$VERSION" ]; then + echo "$VERSION" + else + # Try alternative method using inventory + if [ -f /etc/oraInst.loc ]; then + INVENTORY_LOC=$(cat /etc/oraInst.loc | grep inventory_loc | cut -d= -f2) + if [ -f "${INVENTORY_LOC}/ContentsXML/inventory.xml" ]; then + grep -A 10 -B 2 "$(basename ${ORACLE_HOME_PATH})" "${INVENTORY_LOC}/ContentsXML/inventory.xml" | + grep -o "VER=\"[^\"]*\"" | head -1 | cut -d= -f2 | tr -d '"' + fi + fi + fi + fi + register: detected_oracle_ver + failed_when: false + changed_when: false + + - name: Display detected Oracle version if found + debug: + msg: "Detected Oracle version: {{ detected_oracle_ver.stdout }}" + when: detected_oracle_ver.stdout | length > 0 + + - name: Update oracle_ver based on detected version if needed + set_fact: + oracle_ver: "{{ detected_oracle_ver.stdout }}" + oracle_ver_base: "{{ detected_oracle_ver.stdout | regex_replace('^([0-9]+\\.[0-9]+).*', '\\1') }}" + when: + - detected_oracle_ver.stdout is defined + - detected_oracle_ver.stdout | length > 0 + - detected_oracle_ver.stdout is regex("^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$") + when: + - calling_script != "install-oracle.sh" + - oracle_binary.stat is defined + - oracle_binary.stat.exists | default(false) | bool + tags: always + +- name: Fail if Oracle binary not found (only for add-database.sh) + fail: + msg: | + Oracle binary not found at {{ oracle_home }}/bin/oracle. + + {{ oratab_homes_list.stdout | default('No information available about existing Oracle homes.') }} + + The --ora-db-home-dir parameter must point to a valid Oracle Database home with a functioning Oracle binary. + This should be the path to the Oracle Database software installation, not the Grid Infrastructure home. + when: + - calling_script == "add-database.sh" + - oracle_binary.stat is defined + - not oracle_binary.stat.exists | default(false) | bool + - oracle_binary_alt is not defined or not oracle_binary_alt.stat.exists | default(false) | bool + tags: always \ No newline at end of file diff --git a/roles/db-backups/tasks/main.yml b/roles/db-backups/tasks/main.yml index 7e4c20221..857670261 100644 --- a/roles/db-backups/tasks/main.yml +++ b/roles/db-backups/tasks/main.yml @@ -24,7 +24,19 @@ mode: u=wrx,go= with_items: - "{{ scripts_dir }}" - - "{{ logs_dir }}" + - "{{ logs_dir }}/{{ oracle_sid }}" + tags: db-backups,add-backups + +- name: Create backup destination directory if it's a filesystem path + become: true + become_user: "{{ oracle_user }}" + file: + path: "{{ backup_dest }}/{{ oracle_sid }}" + state: directory + owner: "{{ oracle_user }}" + group: "{{ oracle_group }}" + mode: u=wrx,go= + when: backup_dest is defined and backup_dest[0] == '/' and backup_dest != '' tags: db-backups,add-backups - name: backup location | nfs mount diff --git a/roles/db-backups/templates/rman_arch_backup.sh.j2 b/roles/db-backups/templates/rman_arch_backup.sh.j2 index 2a0acccbe..c347007e7 100644 --- a/roles/db-backups/templates/rman_arch_backup.sh.j2 +++ b/roles/db-backups/templates/rman_arch_backup.sh.j2 @@ -9,10 +9,10 @@ arch_redundancy=${2} # Archivelog redundancy arch_day_online=${3} # Number of days to keep ts="date +%Y-%m-%d_%H-%M-%S" # Timestamp format start_ts="$($ts)" # Start timestamp -log_dir="{{ logs_dir }}" # A directory for the logs +log_dir="{{ logs_dir }}/${ora_inst_name}" # A directory for the logs - per database type="ARCH" # Type is archivelog backup conn_str="{% if oracle_ver == "11.2.0.4.0" %}/{% else %}/ AS SYSBACKUP{% endif %}" # Oracle connection string -backup_dest="{{ backup_dest }}" # Backup destination path +backup_dest="{{ backup_dest }}/${ora_inst_name}" # Per-database backup destination path fra_dest="{{ reco_destination }}" # Fast recovery area location export NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS" # @@ -35,18 +35,22 @@ fi if [[ "${fra_dest:0:1}" != "/" && "${fra_dest:0:1}" != "+" ]]; then fra_dest="+${fra_dest}" fi +# Standardize the backup destination format if [[ "${backup_dest:0:1}" == "/" ]]; then - # Filesystem destination - autobackup_format="${backup_dest}/${ora_inst_name}_%F" - channel_format="${backup_dest}/${ora_inst_name}_${type}_level_${rman_level}_%U" + # Filesystem destination - no prefix needed + backup_dest_final="${backup_dest}" else - # ASM destination, no need for format specifiers; Add the ASM "+" sign (if not already added) + # ASM destination - add "+" prefix if not already present if [[ "${backup_dest:0:1}" != "+" ]]; then - backup_dest="+${backup_dest}" + backup_dest_final="+${backup_dest}" + else + backup_dest_final="${backup_dest}" fi - autobackup_format="${backup_dest}" - channel_format="${backup_dest}" fi + +# Always use proper format specifiers for both filesystem and ASM destinations +autobackup_format="${backup_dest_final}/${ora_inst_name}_%F" +channel_format="${backup_dest_final}/${ora_inst_name}_${type}_%U" # # We can now build the output file and log file names # @@ -84,11 +88,11 @@ EOF # Success or error output with the name of the logfile # then - printf "\n\t%s\n" "INFO -- $($ts) -- ${type} Level ${rman_level} of instance ${ora_inst_name} has been completed successfully." + printf "\n\t%s\n" "INFO -- $($ts) -- ${type} backup of instance ${ora_inst_name} has been completed successfully." printf "\t%s\n\n" "INFO -- $($ts) -- logfile used by this session: ${logfile}" ret_code=0 else - printf "\n\t%s\n\n" "ERROR -- $($ts) -- ${type} Level ${rman_level} of instance ${ora_inst_name} had errors, please have a look at the logfile: ${logfile}" + printf "\n\t%s\n\n" "ERROR -- $($ts) -- ${type} backup of instance ${ora_inst_name} had errors, please have a look at the logfile: ${logfile}" ret_code=123 fi exit ${ret_code} diff --git a/roles/db-backups/templates/rman_full_backup.sh.j2 b/roles/db-backups/templates/rman_full_backup.sh.j2 index 0ba19bd17..6a8303b41 100644 --- a/roles/db-backups/templates/rman_full_backup.sh.j2 +++ b/roles/db-backups/templates/rman_full_backup.sh.j2 @@ -8,9 +8,9 @@ retention_redundancy=${3} # Retention arch_redundancy=${4} # Archivelog redundancy ts="date +%Y-%m-%d_%H-%M-%S" # Timestamp format start_ts="$($ts)" # Start timestamp -log_dir="{{ logs_dir }}" # A directory for the logs +log_dir="{{ logs_dir }}/${ora_inst_name}" # A directory for the logs - per database conn_str="{% if oracle_ver == "11.2.0.4.0" %}/{% else %}/ AS SYSBACKUP{% endif %}" # Oracle connection string -backup_dest="{{ backup_dest }}" # Backup destination path +backup_dest="{{ backup_dest }}/${ora_inst_name}" # Per-database backup destination path fra_dest="{{ reco_destination }}" # Fast recovery area location export NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS" # @@ -40,18 +40,22 @@ fi if [[ "${fra_dest:0:1}" != "/" && "${fra_dest:0:1}" != "+" ]]; then fra_dest="+${fra_dest}" fi +# Standardize the backup destination format if [[ "${backup_dest:0:1}" == "/" ]]; then - # Filesystem destination - autobackup_format="${backup_dest}/${ora_inst_name}_%F" - channel_format="${backup_dest}/${ora_inst_name}_${type}_level_${rman_level}_%U" + # Filesystem destination - no prefix needed + backup_dest_final="${backup_dest}" else - # ASM destination, no need for format specifiers; Add the ASM "+" sign (if not already added) + # ASM destination - add "+" prefix if not already present if [[ "${backup_dest:0:1}" != "+" ]]; then - backup_dest="+${backup_dest}" + backup_dest_final="+${backup_dest}" + else + backup_dest_final="${backup_dest}" fi - autobackup_format="${backup_dest}" - channel_format="${backup_dest}" fi + +# Always use proper format specifiers for both filesystem and ASM destinations +autobackup_format="${backup_dest_final}/${ora_inst_name}_%F" +channel_format="${backup_dest_final}/${ora_inst_name}_${type}_level_${rman_level}_%U" # # We can now build the output file and log file names # diff --git a/roles/db-create/tasks/main.yml b/roles/db-create/tasks/main.yml index 1b2be67cc..276cecd21 100644 --- a/roles/db-create/tasks/main.yml +++ b/roles/db-create/tasks/main.yml @@ -25,6 +25,12 @@ register: check_oratab tags: db-create +- name: Fail if database already exists + fail: + msg: "DB CREATE IS ABORTED: A database with name {{ db_name }} already exists (found in oratab or running process). Please choose a different database name or remove the existing database." + when: check_oratab.stdout != "0" or pmon_proc.stdout != "0" + tags: db-create + - name: Database exists check results debug: msg: "{{ item }}" @@ -34,8 +40,13 @@ - "{{ check_oratab }}" tags: db-create +# Memory validation is now done in the add-database.sh script +# The script will fail early if there's not enough memory +# Removing all memory validation code from Ansible to avoid duplication + - name: Adjust instance memory values (not 11.2 or 12.1) set_fact: + # No need to convert units, we pass variables with their units intact sga_target_bytes: "{{ sga_target }}" pga_aggtar_bytes: "{{ pga_aggtar }}" when: oracle_ver not in ['11.2.0.4.0','12.1.0.2.0'] @@ -43,19 +54,39 @@ - name: Adjust instance memory values (11.2 and 12.1) set_fact: + # For older Oracle versions, we need to strip the 'M' suffix for compatibility + # But we'll retain the numeric precision by using regex_replace instead of truncating sga_target_bytes: "{{ sga_target | regex_replace('M$', '') }}" pga_aggtar_bytes: "{{ pga_aggtar | regex_replace('M$', '') }}" when: oracle_ver in ['11.2.0.4.0','12.1.0.2.0'] tags: db-create +- name: Show memory settings + debug: + msg: "Using memory settings: SGA_TARGET={{ sga_target }}, PGA_AGGREGATE_TARGET={{ pga_aggtar }}, MEMORY_PERCENT={{ effective_memory_pct | default(memory_pct) }}%" + tags: db-create + +# Note: swlib_unzip_path is already defined in group_vars/all.yml +# No need to set a default here + +- name: Ensure swlib directory exists + file: + path: "{{ swlib_unzip_path }}" + state: directory + owner: "{{ oracle_user | default('oracle') }}" + group: "{{ oracle_group | default('oinstall') }}" + mode: "0755" + become: true + tags: db-create + - name: Create DBCA response file script become: true - become_user: "{{ oracle_user }}" + become_user: "{{ oracle_user | default('oracle') }}" template: src: dbca.rsp.sh.j2 dest: "{{ swlib_unzip_path }}/dbca_{{ db_name }}.rsp.sh" - owner: "{{ oracle_user }}" - group: "{{ oracle_group }}" + owner: "{{ oracle_user | default('oracle') }}" + group: "{{ oracle_group | default('oinstall') }}" when: - pmon_proc.stdout == "0" - check_oratab.stdout == "0" @@ -136,7 +167,7 @@ msg: - "{{ dbca_output.cmd }}" - "{{ dbca_output.stdout_lines }}" - # verbosity: 1 + # verbosity: 1 when: - pmon_proc.stdout == "0" - check_oratab.stdout == "0" @@ -145,9 +176,9 @@ - name: Save PDBs state shell: | set -o pipefail - sqlplus / as sysdba << EOF + sqlplus / as sysdba << EOFPDB alter pluggable database all save state; - EOF + EOFPDB environment: ORACLE_HOME: "{{ oracle_home }}" ORACLE_SID: "{{ oracle_sid }}" @@ -167,3 +198,74 @@ name: "{{ systemd_service_name }}" when: free_edition tags: db-create + +- name: Create script to register database with listener + become: true + template: + src: register_db_listener.sh.j2 + dest: "{{ swlib_unzip_path }}/register_db_{{ db_name }}.sh" + owner: "{{ oracle_user | default('oracle') }}" + group: "{{ oracle_group | default('oinstall') }}" + mode: "0755" + when: + - dbca_output is defined + - dbca_output.changed | default(false) + - not free_edition | bool + tags: db-create + +- name: Execute script to register database with listener + become: true + become_user: "{{ oracle_user | default('oracle') }}" + shell: "{{ swlib_unzip_path }}/register_db_{{ db_name }}.sh" + environment: + ORACLE_HOME: "{{ oracle_home }}" + ORACLE_SID: "{{ oracle_sid }}" + PATH: "{{ oracle_home }}/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin" + DB_NAME: "{{ db_name }}" + LISTENER_NAME: "{{ listener_name | default('LISTENER') }}" + LISTENER_PORT: "{{ listener_port | default('1521') }}" + PDB_PREFIX: "{{ pdb_prefix | default('PDB') }}" + PDB_COUNT: "{{ pdb_count | default('1') }}" + ORACLE_VER: "{{ oracle_ver }}" + DB_CONTAINER: "{{ db_container_flag | default(container_db) | default('FALSE') | upper }}" + register: register_script_result + failed_when: + - register_script_result.rc != 0 + - "'WARNING:' not in register_script_result.stdout" + when: + - dbca_output is defined + - dbca_output.changed | default(false) + - not free_edition | bool + tags: db-create + +- name: Display registration script output + debug: + msg: "{{ register_script_result.stdout_lines | default([]) }}" + when: + - register_script_result is defined + - register_script_result.stdout_lines is defined + tags: db-create + +- name: Remove registration script + file: + path: "{{ swlib_unzip_path }}/register_db_{{ db_name }}.sh" + state: absent + when: + - dbca_output is defined + - dbca_output.changed | default(false) + - not free_edition | bool + tags: db-create + +- name: Configure control file autobackup format + become: true + become_user: "{{ oracle_user }}" + shell: | + export ORACLE_HOME={{ oracle_home }} + export PATH=$ORACLE_HOME/bin:$PATH + $ORACLE_HOME/bin/sqlplus / as sysdba << EOF + CONFIGURE CONTROLFILE AUTOBACKUP FORMAT FOR DEVICE TYPE DISK TO '+RECO/SWINGBENCH/%F'; + EOF + when: + - oracle_ver != '11.2.0.4.0' + - pdb_prefix is defined + tags: db-create \ No newline at end of file diff --git a/roles/db-create/tasks/rac-db-create.yml b/roles/db-create/tasks/rac-db-create.yml index 65622c220..9c7194ca3 100644 --- a/roles/db-create/tasks/rac-db-create.yml +++ b/roles/db-create/tasks/rac-db-create.yml @@ -241,7 +241,9 @@ ORACLE_SID: "{{ oracle_sid }}" ORACLE_HOME: "{{ oracle_home }}" PATH: "{{ oracle_home }}/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin" - when: oracle_ver != '11.2.0.4.0' + when: + - oracle_ver != '11.2.0.4.0' + - lookup('env', 'SKIP_DATAPATCH') | default('false') | lower != 'true' register: datapatch_output ignore_errors: true become: true @@ -250,7 +252,10 @@ - name: rac-db-create | Show datapatch results debug: var: datapatch_output.stdout_lines - when: oracle_ver != '11.2.0.4.0' + when: + - oracle_ver != '11.2.0.4.0' + - lookup('env', 'SKIP_DATAPATCH') | default('false') | lower != 'true' + - datapatch_output is defined - name: rac-db-create | Set cluster_database to true shell: | diff --git a/roles/db-create/templates/dbca.rsp.sh.j2 b/roles/db-create/templates/dbca.rsp.sh.j2 index f72a5c2bc..f384174ed 100644 --- a/roles/db-create/templates/dbca.rsp.sh.j2 +++ b/roles/db-create/templates/dbca.rsp.sh.j2 @@ -32,5 +32,8 @@ else sed -i '/^\#\{0,\} \{0,\}storageType \{0,\}=/I s~\#\{0,\} \{0,\}storageType \{0,\}=.*~storageType=ASM~I' "${response_file}" sed -i '/^\#\{0,\} \{0,\}diskGroupName \{0,\}=/I s~\#\{0,\} \{0,\}diskGroupName \{0,\}=.*~diskGroupName='{{ data_destination }}'~I' "${response_file}" sed -i '/^\#\{0,\} \{0,\}recoveryGroupName \{0,\}=/I s~\#\{0,\} \{0,\}recoveryGroupName \{0,\}=.*~recoveryGroupName='{{ reco_destination }}'~I' "${response_file}" - sed -i '/^\#\{0,\} \{0,\}initParams \{0,\}=/I s~\#\{0,\} \{0,\}initParams \{0,\}=.*~initParams=pga_aggregate_target='{{ pga_aggtar_bytes }}',sga_target='{{ sga_target_bytes }}',streams_pool_size=64M,use_large_pages=ONLY,db_domain='{{ db_domain }}',diagnostic_dest='{{ oracle_base }}'~I' "${response_file}" + + # Ensure consistent memory unit handling - SGA and PGA values should always have proper units + # This ensures proper data type conversion by explicitly handling the units + sed -i '/^\#\{0,\} \{0,\}initParams \{0,\}=/I s~\#\{0,\} \{0,\}initParams \{0,\}=.*~initParams=pga_aggregate_target={{ pga_aggtar }},sga_target={{ sga_target }},streams_pool_size=64M,use_large_pages=ONLY,db_domain='{{ db_domain }}',diagnostic_dest='{{ oracle_base }}'~I' "${response_file}" fi diff --git a/roles/db-create/templates/register_db_listener.sh.j2 b/roles/db-create/templates/register_db_listener.sh.j2 new file mode 100644 index 000000000..8e876315e --- /dev/null +++ b/roles/db-create/templates/register_db_listener.sh.j2 @@ -0,0 +1,140 @@ +#!/bin/bash +# Generated by Oracle Toolkit - Database Listener Registration Script + +# Environment is passed through environment variables from Ansible +# ORACLE_HOME, ORACLE_SID, PATH, DB_NAME, LISTENER_NAME, LISTENER_PORT, +# PDB_PREFIX, PDB_COUNT, ORACLE_VER, DB_CONTAINER + +# Get FQDN for better host resolution +HOST_NAME=$(hostname -f 2>/dev/null || hostname) + +# Verify ORACLE_HOME is set +if [ -z "$ORACLE_HOME" ]; then + echo "ERROR: ORACLE_HOME environment variable is not set." + exit 1 +fi + +# Verify ORACLE_SID is set +if [ -z "$ORACLE_SID" ]; then + echo "ERROR: ORACLE_SID environment variable is not set." + exit 1 +fi + +# Create tnsnames.ora entry if it doesn't exist +if [ ! -f $ORACLE_HOME/network/admin/tnsnames.ora ] || ! grep -q "^$DB_NAME" $ORACLE_HOME/network/admin/tnsnames.ora; then + mkdir -p $ORACLE_HOME/network/admin + + echo "# TNS Entry for $DB_NAME created by Oracle Toolkit" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo "$DB_NAME =" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " (DESCRIPTION =" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " (ADDRESS = (PROTOCOL = TCP)(HOST = ${HOST_NAME})(PORT = ${LISTENER_PORT}))" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " (CONNECT_DATA =" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " (SERVER = DEDICATED)" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " (SERVICE_NAME = ${DB_NAME})" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " )" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " )" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo "" >> $ORACLE_HOME/network/admin/tnsnames.ora + + echo "Created tnsnames.ora entry for $DB_NAME" +fi + +# If there's a PDB, add the PDB entry as well +if [[ "$ORACLE_VER" != "11.2.0.4.0" && "$DB_CONTAINER" == "TRUE" ]]; then + for i in $(seq 1 ${PDB_COUNT}); do + PDB_NAME="${PDB_PREFIX}$i" + if ! grep -q "^$PDB_NAME" $ORACLE_HOME/network/admin/tnsnames.ora; then + echo "# TNS Entry for $PDB_NAME created by Oracle Toolkit" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo "$PDB_NAME =" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " (DESCRIPTION =" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " (ADDRESS = (PROTOCOL = TCP)(HOST = ${HOST_NAME})(PORT = ${LISTENER_PORT}))" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " (CONNECT_DATA =" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " (SERVER = DEDICATED)" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " (SERVICE_NAME = $PDB_NAME)" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " )" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo " )" >> $ORACLE_HOME/network/admin/tnsnames.ora + echo "" >> $ORACLE_HOME/network/admin/tnsnames.ora + + echo "Created tnsnames.ora entry for $PDB_NAME" + fi + done +fi + +# Verify database is running before attempting to register +DB_STATUS=$( +$ORACLE_HOME/bin/sqlplus -s / as sysdba << EOF +set heading off +set feedback off +set pagesize 0 +select 'DATABASE_STATUS=OPEN' from v\$database where open_mode = 'READ WRITE'; +exit; +EOF +) + +# Register services with the listener +if [[ "$DB_STATUS" == *"DATABASE_STATUS=OPEN"* ]]; then + echo "Registering database services with listener..." + + # First check if listener is running + LISTENER_STATUS=$($ORACLE_HOME/bin/lsnrctl status $LISTENER_NAME 2>/dev/null) + LISTENER_RUNNING=$? + + if [ $LISTENER_RUNNING -eq 0 ]; then + # Listener is running, register the database + REGISTER_RESULT=$( + $ORACLE_HOME/bin/sqlplus -s / as sysdba << EOF +ALTER SYSTEM REGISTER; +exit; +EOF + ) + + if [ $? -eq 0 ]; then + echo "Successfully registered database with listener" + + # Verify service registration + sleep 5 # Wait for registration to complete + SERVICE_CHECK=$($ORACLE_HOME/bin/lsnrctl status $LISTENER_NAME | grep -i "Service \"$ORACLE_SID\"") + + if [ $? -eq 0 ]; then + echo "Verified service $ORACLE_SID is registered with listener" + else + echo "WARNING: Service $ORACLE_SID not showing in listener services. Will retry registration." + RETRY_RESULT=$( + $ORACLE_HOME/bin/sqlplus -s / as sysdba << EOF +ALTER SYSTEM REGISTER; +exit; +EOF + ) + fi + else + echo "WARNING: Failed to register database with listener" + exit 1 + fi + else + echo "WARNING: Listener $LISTENER_NAME is not running. Will try to start it." + + # Try to start the listener + $ORACLE_HOME/bin/lsnrctl start $LISTENER_NAME + + if [ $? -eq 0 ]; then + echo "Successfully started listener. Registering database." + START_RESULT=$( + $ORACLE_HOME/bin/sqlplus -s / as sysdba << EOF +ALTER SYSTEM REGISTER; +exit; +EOF + ) + + if [ $? -eq 0 ]; then + echo "Successfully registered database with listener after starting it" + else + echo "WARNING: Failed to register database with listener after starting it" + fi + else + echo "WARNING: Failed to start listener. Database will not be registered." + fi + fi +else + echo "WARNING: Database not open. Skipping listener registration." +fi + +exit 0 \ No newline at end of file diff --git a/roles/lsnr-create/tasks/main.yml b/roles/lsnr-create/tasks/main.yml index 3dadcfa7c..8f96b38f8 100644 --- a/roles/lsnr-create/tasks/main.yml +++ b/roles/lsnr-create/tasks/main.yml @@ -55,6 +55,38 @@ when: create_listener tags: lsnr-create +- name: Check if srvctl is available + become: true + shell: "which srvctl || echo 'NOT_FOUND'" + register: srvctl_check + failed_when: false + changed_when: false + when: + - create_listener | bool + - lsnr_port_check.stdout == "0" + - lsnr_name_check.stdout == "0" + tags: lsnr-create + +- name: Check grid home existence + stat: + path: "{{ grid_home | default('/u01/app/grid') }}/bin/srvctl" + register: grid_home_check + failed_when: false + when: + - create_listener | bool + - lsnr_port_check.stdout == "0" + - lsnr_name_check.stdout == "0" + tags: lsnr-create + +- name: Display srvctl status + debug: + msg: "{{ 'srvctl available: ' + srvctl_check.stdout if (srvctl_check is defined and srvctl_check.stdout != 'NOT_FOUND') else 'Warning: srvctl command not found. Grid Infrastructure may not be installed or properly configured.' }}" + when: + - create_listener | bool + - lsnr_port_check.stdout == "0" + - lsnr_name_check.stdout == "0" + tags: lsnr-create + - name: Create listener via srvctl become: true become_user: "{{ grid_user }}" @@ -70,6 +102,8 @@ - lsnr_port_check.stdout == "0" - lsnr_name_check.stdout == "0" - not free_edition + - srvctl_check.stdout != "NOT_FOUND" + - grid_home_check.stat.exists | default(false) tags: lsnr-create - name: Listener creation output @@ -81,7 +115,43 @@ when: lsnr_output.cmd is defined tags: lsnr-create -- name: Create listener via netca +- name: Initialize flag for grid unavailability + set_fact: + grid_unavailable: false + when: + - create_listener | bool + - lsnr_port_check.stdout == "0" + - lsnr_name_check.stdout == "0" + tags: lsnr-create + +- name: Set grid unavailability flag if srvctl is not found + set_fact: + grid_unavailable: true + when: + - create_listener | bool + - lsnr_port_check.stdout == "0" + - lsnr_name_check.stdout == "0" + - srvctl_check is defined + - srvctl_check.stdout == "NOT_FOUND" + tags: lsnr-create + +- name: Set grid unavailability flag if grid_home does not exist + set_fact: + grid_unavailable: true + when: + - create_listener | bool + - lsnr_port_check.stdout == "0" + - lsnr_name_check.stdout == "0" + - grid_home_check is defined + - not grid_home_check.stat.exists + tags: lsnr-create + +- name: Set flag for fallback to RDBMS home listener + set_fact: + use_rdbms_home_for_listener: "{{ create_listener | bool and lsnr_port_check.stdout == '0' and lsnr_name_check.stdout == '0' and grid_unavailable and not (free_edition | bool) }}" + tags: lsnr-create + +- name: Create listener via netca for free edition become: true become_user: "{{ oracle_user }}" shell: | @@ -105,6 +175,70 @@ - free_edition tags: lsnr-create +- name: Create network/admin directory for listener + file: + path: "{{ oracle_home }}/network/admin" + state: directory + owner: "{{ oracle_user }}" + group: "{{ oracle_group }}" + mode: '0755' + when: use_rdbms_home_for_listener | bool + tags: lsnr-create + +- name: Check if listener.ora already exists + stat: + path: "{{ oracle_home }}/network/admin/listener.ora" + register: listener_ora_stat + when: use_rdbms_home_for_listener | bool + tags: lsnr-create + +- name: Get hostname for listener configuration + shell: hostname -f 2>/dev/null || hostname + register: hostname_output + changed_when: false + when: + - use_rdbms_home_for_listener | bool + - not listener_ora_stat.stat.exists | default(false) + tags: lsnr-create + +- name: Create listener.ora template for RDBMS home + template: + src: listener.ora.j2 + dest: "{{ oracle_home }}/network/admin/listener.ora" + owner: "{{ oracle_user }}" + group: "{{ oracle_group }}" + mode: '0644' + vars: + listener_host: "{{ hostname_output.stdout }}" + when: + - use_rdbms_home_for_listener | bool + - not listener_ora_stat.stat.exists | default(false) + tags: lsnr-create + +- name: Start listener with RDBMS home + become: true + become_user: "{{ oracle_user }}" + shell: | + export ORACLE_HOME={{ oracle_home }} + export PATH=$ORACLE_HOME/bin:$PATH + + # Start the listener + lsnrctl start {{ listener_name }} + + # Check listener status + lsnrctl status {{ listener_name }} + register: rdbms_listener_output + when: use_rdbms_home_for_listener | bool + tags: lsnr-create + +- name: Display RDBMS listener creation output + debug: + msg: + - "{{ rdbms_listener_output.cmd }}" + - "{{ rdbms_listener_output.stdout_lines }}" + when: rdbms_listener_output.cmd is defined + tags: lsnr-create + - name: Listener creation output from netca debug: msg: diff --git a/roles/lsnr-create/templates/listener.ora.j2 b/roles/lsnr-create/templates/listener.ora.j2 new file mode 100644 index 000000000..df5090166 --- /dev/null +++ b/roles/lsnr-create/templates/listener.ora.j2 @@ -0,0 +1,20 @@ +# Listener configuration file created by Oracle Toolkit +# Created at {{ ansible_date_time.date }} {{ ansible_date_time.time }} + +{{ listener_name }} = + (DESCRIPTION_LIST = + (DESCRIPTION = + (ADDRESS = (PROTOCOL = TCP)(HOST = {{ listener_host }})(PORT = {{ listener_port }})) + ) + ) + +SID_LIST_{{ listener_name }} = + (SID_LIST = + (SID_DESC = + (GLOBAL_DBNAME = {{ db_name }}{% if db_domain is defined and db_domain|length > 0 %}.{{ db_domain }}{% endif %}) + (ORACLE_HOME = {{ oracle_home }}) + (SID_NAME = {{ db_name }}) + ) + ) + +LOGGING_{{ listener_name }} = ON diff --git a/roles/ora-host/defaults/main.yml b/roles/ora-host/defaults/main.yml index 47e6d5768..15108ab94 100644 --- a/roles/ora-host/defaults/main.yml +++ b/roles/ora-host/defaults/main.yml @@ -108,7 +108,7 @@ oracle_required_rpms_el9: # - If memory_pct is set and is less than 50 percent, then use 50 percent # - If memory_pct is set and is 50 percent or more, then use memory_pct # - If memory_pct is not defined, use 65 percent -ram_pct_used: "{{ (50 if memory_pct is defined and memory_pct < 50 else memory_pct) if memory_pct is defined else 65 }}" +ram_pct_used: "{{ (50 if memory_pct is defined and (memory_pct | int) < 50 else (memory_pct | int)) if memory_pct is defined else 65 }}" # Kernel parameters to check and report on pre-adjustment values when verbosity=1: sysctl_params: